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 sha1 sum should be empty
568
self.assertEqual('', entry[1][0][1])
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
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
575
# We should have gotten a real sha1
576
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
579
# The dirblock has been updated
580
self.assertEqual(sha1sum, entry[1][0][1])
581
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
582
state._dirblock_state)
585
# Now, since we are the only one holding a lock, we should be able
586
# to save and have it written to disk
591
# Re-open the file, and ensure that the state has been updated.
592
state = dirstate.DirState.on_file('dirstate')
595
entry = state._get_entry(0, path_utf8='a-file')
596
self.assertEqual(sha1sum, entry[1][0][1])
600
def test_save_fails_quietly_if_locked(self):
601
"""If dirstate is locked, save will fail without complaining."""
602
self.build_tree(['a-file'])
603
state = dirstate.DirState.initialize('dirstate')
605
# No stat and no sha1 sum.
606
state.add('a-file', 'a-file-id', 'file', None, '')
611
state = dirstate.DirState.on_file('dirstate')
614
entry = state._get_entry(0, path_utf8='a-file')
615
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
616
# We should have gotten a real sha1
617
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
619
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
620
state._dirblock_state)
622
# Now, before we try to save, grab another dirstate, and take out a
624
# TODO: jam 20070315 Ideally this would be locked by another
625
# process. To make sure the file is really OS locked.
626
state2 = dirstate.DirState.on_file('dirstate')
629
# This won't actually write anything, because it couldn't grab
630
# a write lock. But it shouldn't raise an error, either.
631
# TODO: jam 20070315 We should probably distinguish between
632
# being dirty because of 'update_entry'. And dirty
633
# because of real modification. So that save() *does*
634
# raise a real error if it fails when we have real
642
# The file on disk should not be modified.
643
state = dirstate.DirState.on_file('dirstate')
646
entry = state._get_entry(0, path_utf8='a-file')
647
self.assertEqual('', entry[1][0][1])
651
def test_save_refuses_if_changes_aborted(self):
652
self.build_tree(['a-file', 'a-dir/'])
653
state = dirstate.DirState.initialize('dirstate')
655
# No stat and no sha1 sum.
656
state.add('a-file', 'a-file-id', 'file', None, '')
661
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
663
('', [(('', '', 'TREE_ROOT'),
664
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
665
('', [(('', 'a-file', 'a-file-id'),
666
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
669
state = dirstate.DirState.on_file('dirstate')
672
state._read_dirblocks_if_needed()
673
self.assertEqual(expected_blocks, state._dirblocks)
675
# Now modify the state, but mark it as inconsistent
676
state.add('a-dir', 'a-dir-id', 'directory', None, '')
677
state._changes_aborted = True
682
state = dirstate.DirState.on_file('dirstate')
685
state._read_dirblocks_if_needed()
686
self.assertEqual(expected_blocks, state._dirblocks)
691
class TestDirStateInitialize(TestCaseWithDirState):
693
def test_initialize(self):
694
expected_result = ([], [
695
(('', '', 'TREE_ROOT'), # common details
696
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
699
state = dirstate.DirState.initialize('dirstate')
701
self.assertIsInstance(state, dirstate.DirState)
702
lines = state.get_lines()
705
# On win32 you can't read from a locked file, even within the same
706
# process. So we have to unlock and release before we check the file
708
self.assertFileEqual(''.join(lines), 'dirstate')
709
state.lock_read() # check_state_with_reopen will unlock
710
self.check_state_with_reopen(expected_result, state)
713
class TestDirStateManipulations(TestCaseWithDirState):
715
def test_set_state_from_inventory_no_content_no_parents(self):
716
# setting the current inventory is a slow but important api to support.
717
tree1 = self.make_branch_and_memory_tree('tree1')
721
revid1 = tree1.commit('foo').encode('utf8')
722
root_id = tree1.get_root_id()
723
inv = tree1.inventory
726
expected_result = [], [
727
(('', '', root_id), [
728
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
729
state = dirstate.DirState.initialize('dirstate')
731
state.set_state_from_inventory(inv)
732
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
734
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
735
state._dirblock_state)
740
# This will unlock it
741
self.check_state_with_reopen(expected_result, state)
743
def test_set_state_from_inventory_preserves_hashcache(self):
744
# https://bugs.launchpad.net/bzr/+bug/146176
745
# set_state_from_inventory should preserve the stat and hash value for
746
# workingtree files that are not changed by the inventory.
748
tree = self.make_branch_and_tree('.')
749
# depends on the default format using dirstate...
752
# make a dirstate with some valid hashcache data
753
# file on disk, but that's not needed for this test
754
foo_contents = 'contents of foo'
755
self.build_tree_contents([('foo', foo_contents)])
756
tree.add('foo', 'foo-id')
758
foo_stat = os.stat('foo')
759
foo_packed = dirstate.pack_stat(foo_stat)
760
foo_sha = osutils.sha_string(foo_contents)
761
foo_size = len(foo_contents)
763
# should not be cached yet, because the file's too fresh
765
(('', 'foo', 'foo-id',),
766
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
767
tree._dirstate._get_entry(0, 'foo-id'))
768
# poke in some hashcache information - it wouldn't normally be
769
# stored because it's too fresh
770
tree._dirstate.update_minimal(
771
('', 'foo', 'foo-id'),
772
'f', False, foo_sha, foo_packed, foo_size, 'foo')
773
# now should be cached
775
(('', 'foo', 'foo-id',),
776
[('f', foo_sha, foo_size, False, foo_packed)]),
777
tree._dirstate._get_entry(0, 'foo-id'))
779
# extract the inventory, and add something to it
780
inv = tree._get_inventory()
781
# should see the file we poked in...
782
self.assertTrue(inv.has_id('foo-id'))
783
self.assertTrue(inv.has_filename('foo'))
784
inv.add_path('bar', 'file', 'bar-id')
785
tree._dirstate._validate()
786
# this used to cause it to lose its hashcache
787
tree._dirstate.set_state_from_inventory(inv)
788
tree._dirstate._validate()
794
# now check that the state still has the original hashcache value
795
state = tree._dirstate
797
foo_tuple = state._get_entry(0, path_utf8='foo')
799
(('', 'foo', 'foo-id',),
800
[('f', foo_sha, len(foo_contents), False,
801
dirstate.pack_stat(foo_stat))]),
807
def test_set_state_from_inventory_mixed_paths(self):
808
tree1 = self.make_branch_and_tree('tree1')
809
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
810
'tree1/a/b/foo', 'tree1/a-b/bar'])
813
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
814
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
815
tree1.commit('rev1', rev_id='rev1')
816
root_id = tree1.get_root_id()
817
inv = tree1.inventory
820
expected_result1 = [('', '', root_id, 'd'),
821
('', 'a', 'a-id', 'd'),
822
('', 'a-b', 'a-b-id', 'd'),
823
('a', 'b', 'b-id', 'd'),
824
('a/b', 'foo', 'foo-id', 'f'),
825
('a-b', 'bar', 'bar-id', 'f'),
827
expected_result2 = [('', '', root_id, 'd'),
828
('', 'a', 'a-id', 'd'),
829
('', 'a-b', 'a-b-id', 'd'),
830
('a-b', 'bar', 'bar-id', 'f'),
832
state = dirstate.DirState.initialize('dirstate')
834
state.set_state_from_inventory(inv)
836
for entry in state._iter_entries():
837
values.append(entry[0] + entry[1][0][:1])
838
self.assertEqual(expected_result1, values)
840
state.set_state_from_inventory(inv)
842
for entry in state._iter_entries():
843
values.append(entry[0] + entry[1][0][:1])
844
self.assertEqual(expected_result2, values)
848
def test_set_path_id_no_parents(self):
849
"""The id of a path can be changed trivally with no parents."""
850
state = dirstate.DirState.initialize('dirstate')
852
# check precondition to be sure the state does change appropriately.
854
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
855
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
856
list(state._iter_entries()))
857
state.set_path_id('', 'foobarbaz')
859
(('', '', 'foobarbaz'), [('d', '', 0, False,
860
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
861
self.assertEqual(expected_rows, list(state._iter_entries()))
862
# should work across save too
866
state = dirstate.DirState.on_file('dirstate')
870
self.assertEqual(expected_rows, list(state._iter_entries()))
874
def test_set_path_id_with_parents(self):
875
"""Set the root file id in a dirstate with parents"""
876
mt = self.make_branch_and_tree('mt')
877
# in case the default tree format uses a different root id
878
mt.set_root_id('TREE_ROOT')
879
mt.commit('foo', rev_id='parent-revid')
880
rt = mt.branch.repository.revision_tree('parent-revid')
881
state = dirstate.DirState.initialize('dirstate')
884
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
885
state.set_path_id('', 'foobarbaz')
887
# now see that it is what we expected
889
(('', '', 'TREE_ROOT'),
890
[('a', '', 0, False, ''),
891
('d', '', 0, False, 'parent-revid'),
893
(('', '', 'foobarbaz'),
894
[('d', '', 0, False, ''),
895
('a', '', 0, False, ''),
899
self.assertEqual(expected_rows, list(state._iter_entries()))
900
# should work across save too
904
# now flush & check we get the same
905
state = dirstate.DirState.on_file('dirstate')
909
self.assertEqual(expected_rows, list(state._iter_entries()))
912
# now change within an existing file-backed state
916
state.set_path_id('', 'tree-root-2')
922
def test_set_parent_trees_no_content(self):
923
# set_parent_trees is a slow but important api to support.
924
tree1 = self.make_branch_and_memory_tree('tree1')
928
revid1 = tree1.commit('foo')
931
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
932
tree2 = MemoryTree.create_on_branch(branch2)
935
revid2 = tree2.commit('foo')
936
root_id = tree2.get_root_id()
939
state = dirstate.DirState.initialize('dirstate')
941
state.set_path_id('', root_id)
942
state.set_parent_trees(
943
((revid1, tree1.branch.repository.revision_tree(revid1)),
944
(revid2, tree2.branch.repository.revision_tree(revid2)),
945
('ghost-rev', None)),
947
# check we can reopen and use the dirstate after setting parent
954
state = dirstate.DirState.on_file('dirstate')
957
self.assertEqual([revid1, revid2, 'ghost-rev'],
958
state.get_parent_ids())
959
# iterating the entire state ensures that the state is parsable.
960
list(state._iter_entries())
961
# be sure that it sets not appends - change it
962
state.set_parent_trees(
963
((revid1, tree1.branch.repository.revision_tree(revid1)),
964
('ghost-rev', None)),
966
# and now put it back.
967
state.set_parent_trees(
968
((revid1, tree1.branch.repository.revision_tree(revid1)),
969
(revid2, tree2.branch.repository.revision_tree(revid2)),
970
('ghost-rev', tree2.branch.repository.revision_tree(
971
_mod_revision.NULL_REVISION))),
973
self.assertEqual([revid1, revid2, 'ghost-rev'],
974
state.get_parent_ids())
975
# the ghost should be recorded as such by set_parent_trees.
976
self.assertEqual(['ghost-rev'], state.get_ghosts())
978
[(('', '', root_id), [
979
('d', '', 0, False, dirstate.DirState.NULLSTAT),
980
('d', '', 0, False, revid1),
981
('d', '', 0, False, revid2)
983
list(state._iter_entries()))
987
def test_set_parent_trees_file_missing_from_tree(self):
988
# Adding a parent tree may reference files not in the current state.
989
# they should get listed just once by id, even if they are in two
991
# set_parent_trees is a slow but important api to support.
992
tree1 = self.make_branch_and_memory_tree('tree1')
996
tree1.add(['a file'], ['file-id'], ['file'])
997
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
998
revid1 = tree1.commit('foo')
1001
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1002
tree2 = MemoryTree.create_on_branch(branch2)
1005
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1006
revid2 = tree2.commit('foo')
1007
root_id = tree2.get_root_id()
1010
# check the layout in memory
1011
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1012
(('', '', root_id), [
1013
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1014
('d', '', 0, False, revid1.encode('utf8')),
1015
('d', '', 0, False, revid2.encode('utf8'))
1017
(('', 'a file', 'file-id'), [
1018
('a', '', 0, False, ''),
1019
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1020
revid1.encode('utf8')),
1021
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1022
revid2.encode('utf8'))
1025
state = dirstate.DirState.initialize('dirstate')
1027
state.set_path_id('', root_id)
1028
state.set_parent_trees(
1029
((revid1, tree1.branch.repository.revision_tree(revid1)),
1030
(revid2, tree2.branch.repository.revision_tree(revid2)),
1036
# check_state_with_reopen will unlock
1037
self.check_state_with_reopen(expected_result, state)
1039
### add a path via _set_data - so we dont need delta work, just
1040
# raw data in, and ensure that it comes out via get_lines happily.
1042
def test_add_path_to_root_no_parents_all_data(self):
1043
# The most trivial addition of a path is when there are no parents and
1044
# its in the root and all data about the file is supplied
1045
self.build_tree(['a file'])
1046
stat = os.lstat('a file')
1047
# the 1*20 is the sha1 pretend value.
1048
state = dirstate.DirState.initialize('dirstate')
1049
expected_entries = [
1050
(('', '', 'TREE_ROOT'), [
1051
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1053
(('', 'a file', 'a-file-id'), [
1054
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1058
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1059
# having added it, it should be in the output of iter_entries.
1060
self.assertEqual(expected_entries, list(state._iter_entries()))
1061
# saving and reloading should not affect this.
1065
state = dirstate.DirState.on_file('dirstate')
1068
self.assertEqual(expected_entries, list(state._iter_entries()))
1072
def test_add_path_to_unversioned_directory(self):
1073
"""Adding a path to an unversioned directory should error.
1075
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1076
once dirstate is stable and if it is merged with WorkingTree3, consider
1077
removing this copy of the test.
1079
self.build_tree(['unversioned/', 'unversioned/a file'])
1080
state = dirstate.DirState.initialize('dirstate')
1082
self.assertRaises(errors.NotVersionedError, state.add,
1083
'unversioned/a file', 'a-file-id', 'file', None, None)
1087
def test_add_directory_to_root_no_parents_all_data(self):
1088
# The most trivial addition of a dir is when there are no parents and
1089
# its in the root and all data about the file is supplied
1090
self.build_tree(['a dir/'])
1091
stat = os.lstat('a dir')
1092
expected_entries = [
1093
(('', '', 'TREE_ROOT'), [
1094
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1096
(('', 'a dir', 'a dir id'), [
1097
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1100
state = dirstate.DirState.initialize('dirstate')
1102
state.add('a dir', 'a dir id', 'directory', stat, None)
1103
# having added it, it should be in the output of iter_entries.
1104
self.assertEqual(expected_entries, list(state._iter_entries()))
1105
# saving and reloading should not affect this.
1109
state = dirstate.DirState.on_file('dirstate')
1113
self.assertEqual(expected_entries, list(state._iter_entries()))
1117
def test_add_symlink_to_root_no_parents_all_data(self):
1118
# The most trivial addition of a symlink when there are no parents and
1119
# its in the root and all data about the file is supplied
1120
# bzr doesn't support fake symlinks on windows, yet.
1121
self.requireFeature(SymlinkFeature)
1122
os.symlink('target', 'a link')
1123
stat = os.lstat('a link')
1124
expected_entries = [
1125
(('', '', 'TREE_ROOT'), [
1126
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1128
(('', 'a link', 'a link id'), [
1129
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1132
state = dirstate.DirState.initialize('dirstate')
1134
state.add('a link', 'a link id', 'symlink', stat, 'target')
1135
# having added it, it should be in the output of iter_entries.
1136
self.assertEqual(expected_entries, list(state._iter_entries()))
1137
# saving and reloading should not affect this.
1141
state = dirstate.DirState.on_file('dirstate')
1144
self.assertEqual(expected_entries, list(state._iter_entries()))
1148
def test_add_directory_and_child_no_parents_all_data(self):
1149
# after adding a directory, we should be able to add children to it.
1150
self.build_tree(['a dir/', 'a dir/a file'])
1151
dirstat = os.lstat('a dir')
1152
filestat = os.lstat('a dir/a file')
1153
expected_entries = [
1154
(('', '', 'TREE_ROOT'), [
1155
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1157
(('', 'a dir', 'a dir id'), [
1158
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1160
(('a dir', 'a file', 'a-file-id'), [
1161
('f', '1'*20, 25, False,
1162
dirstate.pack_stat(filestat)), # current tree details
1165
state = dirstate.DirState.initialize('dirstate')
1167
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1168
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1169
# added it, it should be in the output of iter_entries.
1170
self.assertEqual(expected_entries, list(state._iter_entries()))
1171
# saving and reloading should not affect this.
1175
state = dirstate.DirState.on_file('dirstate')
1178
self.assertEqual(expected_entries, list(state._iter_entries()))
1182
def test_add_tree_reference(self):
1183
# make a dirstate and add a tree reference
1184
state = dirstate.DirState.initialize('dirstate')
1186
('', 'subdir', 'subdir-id'),
1187
[('t', 'subtree-123123', 0, False,
1188
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1191
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1192
entry = state._get_entry(0, 'subdir-id', 'subdir')
1193
self.assertEqual(entry, expected_entry)
1198
# now check we can read it back
1202
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1203
self.assertEqual(entry, entry2)
1204
self.assertEqual(entry, expected_entry)
1205
# and lookup by id should work too
1206
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1207
self.assertEqual(entry, expected_entry)
1211
def test_add_forbidden_names(self):
1212
state = dirstate.DirState.initialize('dirstate')
1213
self.addCleanup(state.unlock)
1214
self.assertRaises(errors.BzrError,
1215
state.add, '.', 'ass-id', 'directory', None, None)
1216
self.assertRaises(errors.BzrError,
1217
state.add, '..', 'ass-id', 'directory', None, None)
1220
class TestGetLines(TestCaseWithDirState):
1222
def test_get_line_with_2_rows(self):
1223
state = self.create_dirstate_with_root_and_subdir()
1225
self.assertEqual(['#bazaar dirstate flat format 3\n',
1226
'crc32: 41262208\n',
1230
'\x00\x00a-root-value\x00'
1231
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1232
'\x00subdir\x00subdir-id\x00'
1233
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1234
], state.get_lines())
1238
def test_entry_to_line(self):
1239
state = self.create_dirstate_with_root()
1242
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1243
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1244
state._entry_to_line(state._dirblocks[0][1][0]))
1248
def test_entry_to_line_with_parent(self):
1249
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1250
root_entry = ('', '', 'a-root-value'), [
1251
('d', '', 0, False, packed_stat), # current tree details
1252
# first: a pointer to the current location
1253
('a', 'dirname/basename', 0, False, ''),
1255
state = dirstate.DirState.initialize('dirstate')
1258
'\x00\x00a-root-value\x00'
1259
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1260
'a\x00dirname/basename\x000\x00n\x00',
1261
state._entry_to_line(root_entry))
1265
def test_entry_to_line_with_two_parents_at_different_paths(self):
1266
# / in the tree, at / in one parent and /dirname/basename in the other.
1267
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1268
root_entry = ('', '', 'a-root-value'), [
1269
('d', '', 0, False, packed_stat), # current tree details
1270
('d', '', 0, False, 'rev_id'), # first parent details
1271
# second: a pointer to the current location
1272
('a', 'dirname/basename', 0, False, ''),
1274
state = dirstate.DirState.initialize('dirstate')
1277
'\x00\x00a-root-value\x00'
1278
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1279
'd\x00\x000\x00n\x00rev_id\x00'
1280
'a\x00dirname/basename\x000\x00n\x00',
1281
state._entry_to_line(root_entry))
1285
def test_iter_entries(self):
1286
# we should be able to iterate the dirstate entries from end to end
1287
# this is for get_lines to be easy to read.
1288
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1290
root_entries = [(('', '', 'a-root-value'), [
1291
('d', '', 0, False, packed_stat), # current tree details
1293
dirblocks.append(('', root_entries))
1294
# add two files in the root
1295
subdir_entry = ('', 'subdir', 'subdir-id'), [
1296
('d', '', 0, False, packed_stat), # current tree details
1298
afile_entry = ('', 'afile', 'afile-id'), [
1299
('f', 'sha1value', 34, False, packed_stat), # current tree details
1301
dirblocks.append(('', [subdir_entry, afile_entry]))
1303
file_entry2 = ('subdir', '2file', '2file-id'), [
1304
('f', 'sha1value', 23, False, packed_stat), # current tree details
1306
dirblocks.append(('subdir', [file_entry2]))
1307
state = dirstate.DirState.initialize('dirstate')
1309
state._set_data([], dirblocks)
1310
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1312
self.assertEqual(expected_entries, list(state._iter_entries()))
1317
class TestGetBlockRowIndex(TestCaseWithDirState):
1319
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1320
file_present, state, dirname, basename, tree_index):
1321
self.assertEqual((block_index, row_index, dir_present, file_present),
1322
state._get_block_entry_index(dirname, basename, tree_index))
1324
block = state._dirblocks[block_index]
1325
self.assertEqual(dirname, block[0])
1326
if dir_present and file_present:
1327
row = state._dirblocks[block_index][1][row_index]
1328
self.assertEqual(dirname, row[0][0])
1329
self.assertEqual(basename, row[0][1])
1331
def test_simple_structure(self):
1332
state = self.create_dirstate_with_root_and_subdir()
1333
self.addCleanup(state.unlock)
1334
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1335
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1336
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1337
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1338
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1341
def test_complex_structure_exists(self):
1342
state = self.create_complex_dirstate()
1343
self.addCleanup(state.unlock)
1344
# Make sure we can find everything that exists
1345
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1346
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1347
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1348
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1349
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1350
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1351
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1352
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1353
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1354
'b', 'h\xc3\xa5', 0)
1356
def test_complex_structure_missing(self):
1357
state = self.create_complex_dirstate()
1358
self.addCleanup(state.unlock)
1359
# Make sure things would be inserted in the right locations
1360
# '_' comes before 'a'
1361
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1362
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1363
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1364
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1366
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1367
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1368
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1369
# This would be inserted between a/ and b/
1370
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1372
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1375
class TestGetEntry(TestCaseWithDirState):
1377
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1378
"""Check that the right entry is returned for a request to getEntry."""
1379
entry = state._get_entry(index, path_utf8=path)
1381
self.assertEqual((None, None), entry)
1384
self.assertEqual((dirname, basename, file_id), cur[:3])
1386
def test_simple_structure(self):
1387
state = self.create_dirstate_with_root_and_subdir()
1388
self.addCleanup(state.unlock)
1389
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1390
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1391
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1392
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1393
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1395
def test_complex_structure_exists(self):
1396
state = self.create_complex_dirstate()
1397
self.addCleanup(state.unlock)
1398
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1399
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1400
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1401
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1402
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1403
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1404
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1405
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1406
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1409
def test_complex_structure_missing(self):
1410
state = self.create_complex_dirstate()
1411
self.addCleanup(state.unlock)
1412
self.assertEntryEqual(None, None, None, state, '_', 0)
1413
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1414
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1415
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1417
def test_get_entry_uninitialized(self):
1418
"""Calling get_entry will load data if it needs to"""
1419
state = self.create_dirstate_with_root()
1425
state = dirstate.DirState.on_file('dirstate')
1428
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1429
state._header_state)
1430
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1431
state._dirblock_state)
1432
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1437
class TestIterChildEntries(TestCaseWithDirState):
1439
def create_dirstate_with_two_trees(self):
1440
"""This dirstate contains multiple files and directories.
1450
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1452
Notice that a/e is an empty directory.
1454
There is one parent tree, which has the same shape with the following variations:
1455
b/g in the parent is gone.
1456
b/h in the parent has a different id
1457
b/i is new in the parent
1458
c is renamed to b/j in the parent
1460
:return: The dirstate, still write-locked.
1462
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1463
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1464
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1465
root_entry = ('', '', 'a-root-value'), [
1466
('d', '', 0, False, packed_stat),
1467
('d', '', 0, False, 'parent-revid'),
1469
a_entry = ('', 'a', 'a-dir'), [
1470
('d', '', 0, False, packed_stat),
1471
('d', '', 0, False, 'parent-revid'),
1473
b_entry = ('', 'b', 'b-dir'), [
1474
('d', '', 0, False, packed_stat),
1475
('d', '', 0, False, 'parent-revid'),
1477
c_entry = ('', 'c', 'c-file'), [
1478
('f', null_sha, 10, False, packed_stat),
1479
('r', 'b/j', 0, False, ''),
1481
d_entry = ('', 'd', 'd-file'), [
1482
('f', null_sha, 20, False, packed_stat),
1483
('f', 'd', 20, False, 'parent-revid'),
1485
e_entry = ('a', 'e', 'e-dir'), [
1486
('d', '', 0, False, packed_stat),
1487
('d', '', 0, False, 'parent-revid'),
1489
f_entry = ('a', 'f', 'f-file'), [
1490
('f', null_sha, 30, False, packed_stat),
1491
('f', 'f', 20, False, 'parent-revid'),
1493
g_entry = ('b', 'g', 'g-file'), [
1494
('f', null_sha, 30, False, packed_stat),
1495
NULL_PARENT_DETAILS,
1497
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1498
('f', null_sha, 40, False, packed_stat),
1499
NULL_PARENT_DETAILS,
1501
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1502
NULL_PARENT_DETAILS,
1503
('f', 'h', 20, False, 'parent-revid'),
1505
i_entry = ('b', 'i', 'i-file'), [
1506
NULL_PARENT_DETAILS,
1507
('f', 'h', 20, False, 'parent-revid'),
1509
j_entry = ('b', 'j', 'c-file'), [
1510
('r', 'c', 0, False, ''),
1511
('f', 'j', 20, False, 'parent-revid'),
1514
dirblocks.append(('', [root_entry]))
1515
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1516
dirblocks.append(('a', [e_entry, f_entry]))
1517
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1518
state = dirstate.DirState.initialize('dirstate')
1521
state._set_data(['parent'], dirblocks)
1525
return state, dirblocks
1527
def test_iter_children_b(self):
1528
state, dirblocks = self.create_dirstate_with_two_trees()
1529
self.addCleanup(state.unlock)
1530
expected_result = []
1531
expected_result.append(dirblocks[3][1][2]) # h2
1532
expected_result.append(dirblocks[3][1][3]) # i
1533
expected_result.append(dirblocks[3][1][4]) # j
1534
self.assertEqual(expected_result,
1535
list(state._iter_child_entries(1, 'b')))
1537
def test_iter_child_root(self):
1538
state, dirblocks = self.create_dirstate_with_two_trees()
1539
self.addCleanup(state.unlock)
1540
expected_result = []
1541
expected_result.append(dirblocks[1][1][0]) # a
1542
expected_result.append(dirblocks[1][1][1]) # b
1543
expected_result.append(dirblocks[1][1][3]) # d
1544
expected_result.append(dirblocks[2][1][0]) # e
1545
expected_result.append(dirblocks[2][1][1]) # f
1546
expected_result.append(dirblocks[3][1][2]) # h2
1547
expected_result.append(dirblocks[3][1][3]) # i
1548
expected_result.append(dirblocks[3][1][4]) # j
1549
self.assertEqual(expected_result,
1550
list(state._iter_child_entries(1, '')))
1553
class TestDirstateSortOrder(TestCaseWithTransport):
1554
"""Test that DirState adds entries in the right order."""
1556
def test_add_sorting(self):
1557
"""Add entries in lexicographical order, we get path sorted order.
1559
This tests it to a depth of 4, to make sure we don't just get it right
1560
at a single depth. 'a/a' should come before 'a-a', even though it
1561
doesn't lexicographically.
1563
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1564
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1567
state = dirstate.DirState.initialize('dirstate')
1568
self.addCleanup(state.unlock)
1570
fake_stat = os.stat('dirstate')
1572
d_id = d.replace('/', '_')+'-id'
1573
file_path = d + '/f'
1574
file_id = file_path.replace('/', '_')+'-id'
1575
state.add(d, d_id, 'directory', fake_stat, null_sha)
1576
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1578
expected = ['', '', 'a',
1579
'a/a', 'a/a/a', 'a/a/a/a',
1580
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1582
split = lambda p:p.split('/')
1583
self.assertEqual(sorted(expected, key=split), expected)
1584
dirblock_names = [d[0] for d in state._dirblocks]
1585
self.assertEqual(expected, dirblock_names)
1587
def test_set_parent_trees_correct_order(self):
1588
"""After calling set_parent_trees() we should maintain the order."""
1589
dirs = ['a', 'a-a', 'a/a']
1591
state = dirstate.DirState.initialize('dirstate')
1592
self.addCleanup(state.unlock)
1594
fake_stat = os.stat('dirstate')
1596
d_id = d.replace('/', '_')+'-id'
1597
file_path = d + '/f'
1598
file_id = file_path.replace('/', '_')+'-id'
1599
state.add(d, d_id, 'directory', fake_stat, null_sha)
1600
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1602
expected = ['', '', 'a', 'a/a', 'a-a']
1603
dirblock_names = [d[0] for d in state._dirblocks]
1604
self.assertEqual(expected, dirblock_names)
1606
# *really* cheesy way to just get an empty tree
1607
repo = self.make_repository('repo')
1608
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1609
state.set_parent_trees([('null:', empty_tree)], [])
1611
dirblock_names = [d[0] for d in state._dirblocks]
1612
self.assertEqual(expected, dirblock_names)
1615
class InstrumentedDirState(dirstate.DirState):
1616
"""An DirState with instrumented sha1 functionality."""
1618
def __init__(self, path):
1619
super(InstrumentedDirState, self).__init__(path)
1620
self._time_offset = 0
1622
# member is dynamically set in DirState.__init__ to turn on trace
1623
self._sha1_file = self._sha1_file_and_log
1625
def _sha_cutoff_time(self):
1626
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1627
self._cutoff_time = timestamp + self._time_offset
1629
def _sha1_file_and_log(self, abspath):
1630
self._log.append(('sha1', abspath))
1631
return osutils.sha_file_by_name(abspath)
1633
def _read_link(self, abspath, old_link):
1634
self._log.append(('read_link', abspath, old_link))
1635
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1637
def _lstat(self, abspath, entry):
1638
self._log.append(('lstat', abspath))
1639
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1641
def _is_executable(self, mode, old_executable):
1642
self._log.append(('is_exec', mode, old_executable))
1643
return super(InstrumentedDirState, self)._is_executable(mode,
1646
def adjust_time(self, secs):
1647
"""Move the clock forward or back.
1649
:param secs: The amount to adjust the clock by. Positive values make it
1650
seem as if we are in the future, negative values make it seem like we
1653
self._time_offset += secs
1654
self._cutoff_time = None
1657
class _FakeStat(object):
1658
"""A class with the same attributes as a real stat result."""
1660
def __init__(self, size, mtime, ctime, dev, ino, mode):
1662
self.st_mtime = mtime
1663
self.st_ctime = ctime
1669
class TestPackStat(TestCaseWithTransport):
1671
def assertPackStat(self, expected, stat_value):
1672
"""Check the packed and serialized form of a stat value."""
1673
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1675
def test_pack_stat_int(self):
1676
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1677
# Make sure that all parameters have an impact on the packed stat.
1678
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1681
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1682
st.st_mtime = 1172758620
1684
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1685
st.st_ctime = 1172758630
1687
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1690
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1691
st.st_ino = 6499540L
1693
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1694
st.st_mode = 0100744
1696
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1698
def test_pack_stat_float(self):
1699
"""On some platforms mtime and ctime are floats.
1701
Make sure we don't get warnings or errors, and that we ignore changes <
1704
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1705
777L, 6499538L, 0100644)
1706
# These should all be the same as the integer counterparts
1707
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1708
st.st_mtime = 1172758620.0
1710
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1711
st.st_ctime = 1172758630.0
1713
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1714
# fractional seconds are discarded, so no change from above
1715
st.st_mtime = 1172758620.453
1716
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1717
st.st_ctime = 1172758630.228
1718
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1721
class TestBisect(TestCaseWithDirState):
1722
"""Test the ability to bisect into the disk format."""
1724
def assertBisect(self, expected_map, map_keys, state, paths):
1725
"""Assert that bisecting for paths returns the right result.
1727
:param expected_map: A map from key => entry value
1728
:param map_keys: The keys to expect for each path
1729
:param state: The DirState object.
1730
:param paths: A list of paths, these will automatically be split into
1731
(dir, name) tuples, and sorted according to how _bisect
1734
result = state._bisect(paths)
1735
# For now, results are just returned in whatever order we read them.
1736
# We could sort by (dir, name, file_id) or something like that, but in
1737
# the end it would still be fairly arbitrary, and we don't want the
1738
# extra overhead if we can avoid it. So sort everything to make sure
1740
self.assertEqual(len(map_keys), len(paths))
1742
for path, keys in zip(paths, map_keys):
1744
# This should not be present in the output
1746
expected[path] = sorted(expected_map[k] for k in keys)
1748
# The returned values are just arranged randomly based on when they
1749
# were read, for testing, make sure it is properly sorted.
1753
self.assertEqual(expected, result)
1755
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1756
"""Assert that bisecting for dirbblocks returns the right result.
1758
:param expected_map: A map from key => expected values
1759
:param map_keys: A nested list of paths we expect to be returned.
1760
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1761
:param state: The DirState object.
1762
:param paths: A list of directories
1764
result = state._bisect_dirblocks(paths)
1765
self.assertEqual(len(map_keys), len(paths))
1767
for path, keys in zip(paths, map_keys):
1769
# This should not be present in the output
1771
expected[path] = sorted(expected_map[k] for k in keys)
1775
self.assertEqual(expected, result)
1777
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1778
"""Assert the return value of a recursive bisection.
1780
:param expected_map: A map from key => entry value
1781
:param map_keys: A list of paths we expect to be returned.
1782
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1783
:param state: The DirState object.
1784
:param paths: A list of files and directories. It will be broken up
1785
into (dir, name) pairs and sorted before calling _bisect_recursive.
1788
for key in map_keys:
1789
entry = expected_map[key]
1790
dir_name_id, trees_info = entry
1791
expected[dir_name_id] = trees_info
1793
result = state._bisect_recursive(paths)
1795
self.assertEqual(expected, result)
1797
def test_bisect_each(self):
1798
"""Find a single record using bisect."""
1799
tree, state, expected = self.create_basic_dirstate()
1801
# Bisect should return the rows for the specified files.
1802
self.assertBisect(expected, [['']], state, [''])
1803
self.assertBisect(expected, [['a']], state, ['a'])
1804
self.assertBisect(expected, [['b']], state, ['b'])
1805
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1806
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1807
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1808
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1809
self.assertBisect(expected, [['f']], state, ['f'])
1811
def test_bisect_multi(self):
1812
"""Bisect can be used to find multiple records at the same time."""
1813
tree, state, expected = self.create_basic_dirstate()
1814
# Bisect should be capable of finding multiple entries at the same time
1815
self.assertBisect(expected, [['a'], ['b'], ['f']],
1816
state, ['a', 'b', 'f'])
1817
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1818
state, ['f', 'b/d', 'b/d/e'])
1819
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1820
state, ['b', 'b-c', 'b/c'])
1822
def test_bisect_one_page(self):
1823
"""Test bisect when there is only 1 page to read"""
1824
tree, state, expected = self.create_basic_dirstate()
1825
state._bisect_page_size = 5000
1826
self.assertBisect(expected,[['']], state, [''])
1827
self.assertBisect(expected,[['a']], state, ['a'])
1828
self.assertBisect(expected,[['b']], state, ['b'])
1829
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1830
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1831
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1832
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1833
self.assertBisect(expected,[['f']], state, ['f'])
1834
self.assertBisect(expected,[['a'], ['b'], ['f']],
1835
state, ['a', 'b', 'f'])
1836
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1837
state, ['b/d', 'b/d/e', 'f'])
1838
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1839
state, ['b', 'b/c', 'b-c'])
1841
def test_bisect_duplicate_paths(self):
1842
"""When bisecting for a path, handle multiple entries."""
1843
tree, state, expected = self.create_duplicated_dirstate()
1845
# Now make sure that both records are properly returned.
1846
self.assertBisect(expected, [['']], state, [''])
1847
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1848
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1849
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1850
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1851
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1853
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1854
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1856
def test_bisect_page_size_too_small(self):
1857
"""If the page size is too small, we will auto increase it."""
1858
tree, state, expected = self.create_basic_dirstate()
1859
state._bisect_page_size = 50
1860
self.assertBisect(expected, [None], state, ['b/e'])
1861
self.assertBisect(expected, [['a']], state, ['a'])
1862
self.assertBisect(expected, [['b']], state, ['b'])
1863
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1864
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1865
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1866
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1867
self.assertBisect(expected, [['f']], state, ['f'])
1869
def test_bisect_missing(self):
1870
"""Test that bisect return None if it cannot find a path."""
1871
tree, state, expected = self.create_basic_dirstate()
1872
self.assertBisect(expected, [None], state, ['foo'])
1873
self.assertBisect(expected, [None], state, ['b/foo'])
1874
self.assertBisect(expected, [None], state, ['bar/foo'])
1875
self.assertBisect(expected, [None], state, ['b-c/foo'])
1877
self.assertBisect(expected, [['a'], None, ['b/d']],
1878
state, ['a', 'foo', 'b/d'])
1880
def test_bisect_rename(self):
1881
"""Check that we find a renamed row."""
1882
tree, state, expected = self.create_renamed_dirstate()
1884
# Search for the pre and post renamed entries
1885
self.assertBisect(expected, [['a']], state, ['a'])
1886
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1887
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1888
self.assertBisect(expected, [['h']], state, ['h'])
1890
# What about b/d/e? shouldn't that also get 2 directory entries?
1891
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1892
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1894
def test_bisect_dirblocks(self):
1895
tree, state, expected = self.create_duplicated_dirstate()
1896
self.assertBisectDirBlocks(expected,
1897
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1899
self.assertBisectDirBlocks(expected,
1900
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1901
self.assertBisectDirBlocks(expected,
1902
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1903
self.assertBisectDirBlocks(expected,
1904
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1905
['b/c', 'b/c2', 'b/d', 'b/d2'],
1906
['b/d/e', 'b/d/e2'],
1907
], state, ['', 'b', 'b/d'])
1909
def test_bisect_dirblocks_missing(self):
1910
tree, state, expected = self.create_basic_dirstate()
1911
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1912
state, ['b/d', 'b/e'])
1913
# Files don't show up in this search
1914
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1915
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1916
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1917
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1918
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1920
def test_bisect_recursive_each(self):
1921
tree, state, expected = self.create_basic_dirstate()
1922
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1923
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1924
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1925
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
1926
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1928
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1930
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
1934
def test_bisect_recursive_multiple(self):
1935
tree, state, expected = self.create_basic_dirstate()
1936
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1937
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1938
state, ['b/d', 'b/d/e'])
1940
def test_bisect_recursive_missing(self):
1941
tree, state, expected = self.create_basic_dirstate()
1942
self.assertBisectRecursive(expected, [], state, ['d'])
1943
self.assertBisectRecursive(expected, [], state, ['b/e'])
1944
self.assertBisectRecursive(expected, [], state, ['g'])
1945
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1947
def test_bisect_recursive_renamed(self):
1948
tree, state, expected = self.create_renamed_dirstate()
1950
# Looking for either renamed item should find the other
1951
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1952
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1953
# Looking in the containing directory should find the rename target,
1954
# and anything in a subdir of the renamed target.
1955
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1956
'b/d/e', 'b/g', 'h', 'h/e'],
1960
class TestDirstateValidation(TestCaseWithDirState):
1962
def test_validate_correct_dirstate(self):
1963
state = self.create_complex_dirstate()
1966
# and make sure we can also validate with a read lock
1973
def test_dirblock_not_sorted(self):
1974
tree, state, expected = self.create_renamed_dirstate()
1975
state._read_dirblocks_if_needed()
1976
last_dirblock = state._dirblocks[-1]
1977
# we're appending to the dirblock, but this name comes before some of
1978
# the existing names; that's wrong
1979
last_dirblock[1].append(
1980
(('h', 'aaaa', 'a-id'),
1981
[('a', '', 0, False, ''),
1982
('a', '', 0, False, '')]))
1983
e = self.assertRaises(AssertionError,
1985
self.assertContainsRe(str(e), 'not sorted')
1987
def test_dirblock_name_mismatch(self):
1988
tree, state, expected = self.create_renamed_dirstate()
1989
state._read_dirblocks_if_needed()
1990
last_dirblock = state._dirblocks[-1]
1991
# add an entry with the wrong directory name
1992
last_dirblock[1].append(
1994
[('a', '', 0, False, ''),
1995
('a', '', 0, False, '')]))
1996
e = self.assertRaises(AssertionError,
1998
self.assertContainsRe(str(e),
1999
"doesn't match directory name")
2001
def test_dirblock_missing_rename(self):
2002
tree, state, expected = self.create_renamed_dirstate()
2003
state._read_dirblocks_if_needed()
2004
last_dirblock = state._dirblocks[-1]
2005
# make another entry for a-id, without a correct 'r' pointer to
2006
# the real occurrence in the working tree
2007
last_dirblock[1].append(
2008
(('h', 'z', 'a-id'),
2009
[('a', '', 0, False, ''),
2010
('a', '', 0, False, '')]))
2011
e = self.assertRaises(AssertionError,
2013
self.assertContainsRe(str(e),
2014
'file a-id is absent in row')
2017
class TestDirstateTreeReference(TestCaseWithDirState):
2019
def test_reference_revision_is_none(self):
2020
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2021
subtree = self.make_branch_and_tree('tree/subtree',
2022
format='dirstate-with-subtree')
2023
subtree.set_root_id('subtree')
2024
tree.add_reference(subtree)
2026
state = dirstate.DirState.from_tree(tree, 'dirstate')
2027
key = ('', 'subtree', 'subtree')
2028
expected = ('', [(key,
2029
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2032
self.assertEqual(expected, state._find_block(key))
2037
class TestDiscardMergeParents(TestCaseWithDirState):
2039
def test_discard_no_parents(self):
2040
# This should be a no-op
2041
state = self.create_empty_dirstate()
2042
self.addCleanup(state.unlock)
2043
state._discard_merge_parents()
2046
def test_discard_one_parent(self):
2048
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2049
root_entry_direntry = ('', '', 'a-root-value'), [
2050
('d', '', 0, False, packed_stat),
2051
('d', '', 0, False, packed_stat),
2054
dirblocks.append(('', [root_entry_direntry]))
2055
dirblocks.append(('', []))
2057
state = self.create_empty_dirstate()
2058
self.addCleanup(state.unlock)
2059
state._set_data(['parent-id'], dirblocks[:])
2062
state._discard_merge_parents()
2064
self.assertEqual(dirblocks, state._dirblocks)
2066
def test_discard_simple(self):
2068
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2069
root_entry_direntry = ('', '', 'a-root-value'), [
2070
('d', '', 0, False, packed_stat),
2071
('d', '', 0, False, packed_stat),
2072
('d', '', 0, False, packed_stat),
2074
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2075
('d', '', 0, False, packed_stat),
2076
('d', '', 0, False, packed_stat),
2079
dirblocks.append(('', [root_entry_direntry]))
2080
dirblocks.append(('', []))
2082
state = self.create_empty_dirstate()
2083
self.addCleanup(state.unlock)
2084
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2087
# This should strip of the extra column
2088
state._discard_merge_parents()
2090
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2091
self.assertEqual(expected_dirblocks, state._dirblocks)
2093
def test_discard_absent(self):
2094
"""If entries are only in a merge, discard should remove the entries"""
2095
null_stat = dirstate.DirState.NULLSTAT
2096
present_dir = ('d', '', 0, False, null_stat)
2097
present_file = ('f', '', 0, False, null_stat)
2098
absent = dirstate.DirState.NULL_PARENT_DETAILS
2099
root_key = ('', '', 'a-root-value')
2100
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2101
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2102
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2103
('', [(file_in_merged_key,
2104
[absent, absent, present_file]),
2106
[present_file, present_file, present_file]),
2110
state = self.create_empty_dirstate()
2111
self.addCleanup(state.unlock)
2112
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2115
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2116
('', [(file_in_root_key,
2117
[present_file, present_file]),
2120
state._discard_merge_parents()
2122
self.assertEqual(exp_dirblocks, state._dirblocks)
2124
def test_discard_renamed(self):
2125
null_stat = dirstate.DirState.NULLSTAT
2126
present_dir = ('d', '', 0, False, null_stat)
2127
present_file = ('f', '', 0, False, null_stat)
2128
absent = dirstate.DirState.NULL_PARENT_DETAILS
2129
root_key = ('', '', 'a-root-value')
2130
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2131
# Renamed relative to parent
2132
file_rename_s_key = ('', 'file-s', 'b-file-id')
2133
file_rename_t_key = ('', 'file-t', 'b-file-id')
2134
# And one that is renamed between the parents, but absent in this
2135
key_in_1 = ('', 'file-in-1', 'c-file-id')
2136
key_in_2 = ('', 'file-in-2', 'c-file-id')
2139
('', [(root_key, [present_dir, present_dir, present_dir])]),
2141
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2143
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2145
[present_file, present_file, present_file]),
2147
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2149
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2153
('', [(root_key, [present_dir, present_dir])]),
2154
('', [(key_in_1, [absent, present_file]),
2155
(file_in_root_key, [present_file, present_file]),
2156
(file_rename_t_key, [present_file, absent]),
2159
state = self.create_empty_dirstate()
2160
self.addCleanup(state.unlock)
2161
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2164
state._discard_merge_parents()
2166
self.assertEqual(exp_dirblocks, state._dirblocks)
2168
def test_discard_all_subdir(self):
2169
null_stat = dirstate.DirState.NULLSTAT
2170
present_dir = ('d', '', 0, False, null_stat)
2171
present_file = ('f', '', 0, False, null_stat)
2172
absent = dirstate.DirState.NULL_PARENT_DETAILS
2173
root_key = ('', '', 'a-root-value')
2174
subdir_key = ('', 'sub', 'dir-id')
2175
child1_key = ('sub', 'child1', 'child1-id')
2176
child2_key = ('sub', 'child2', 'child2-id')
2177
child3_key = ('sub', 'child3', 'child3-id')
2180
('', [(root_key, [present_dir, present_dir, present_dir])]),
2181
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2182
('sub', [(child1_key, [absent, absent, present_file]),
2183
(child2_key, [absent, absent, present_file]),
2184
(child3_key, [absent, absent, present_file]),
2188
('', [(root_key, [present_dir, present_dir])]),
2189
('', [(subdir_key, [present_dir, present_dir])]),
2192
state = self.create_empty_dirstate()
2193
self.addCleanup(state.unlock)
2194
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2197
state._discard_merge_parents()
2199
self.assertEqual(exp_dirblocks, state._dirblocks)
2202
class Test_InvEntryToDetails(TestCaseWithDirState):
2204
def assertDetails(self, expected, inv_entry):
2205
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2206
self.assertEqual(expected, details)
2207
# details should always allow join() and always be a plain str when
2209
(minikind, fingerprint, size, executable, tree_data) = details
2210
self.assertIsInstance(minikind, str)
2211
self.assertIsInstance(fingerprint, str)
2212
self.assertIsInstance(tree_data, str)
2214
def test_unicode_symlink(self):
2215
# In general, the code base doesn't support a target that contains
2216
# non-ascii characters. So we just assert tha
2217
inv_entry = inventory.InventoryLink('link-file-id', 'name',
2219
inv_entry.revision = 'link-revision-id'
2220
inv_entry.symlink_target = u'link-target'
2221
details = self.assertDetails(('l', 'link-target', 0, False,
2222
'link-revision-id'), inv_entry)