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
from bzrlib.memorytree import MemoryTree
29
from bzrlib.osutils import has_symlinks
30
from bzrlib.tests import (
32
TestCaseWithTransport,
39
# general checks for NOT_IN_MEMORY error conditions.
40
# set_path_id on a NOT_IN_MEMORY dirstate
41
# set_path_id unicode support
42
# set_path_id setting id of a path not root
43
# set_path_id setting id when there are parents without the id in the parents
44
# set_path_id setting id when there are parents with the id in the parents
45
# set_path_id setting id when state is not in memory
46
# set_path_id setting id when state is in memory unmodified
47
# set_path_id setting id when state is in memory modified
50
class TestCaseWithDirState(TestCaseWithTransport):
51
"""Helper functions for creating DirState objects with various content."""
53
def create_empty_dirstate(self):
54
"""Return a locked but empty dirstate"""
55
state = dirstate.DirState.initialize('dirstate')
58
def create_dirstate_with_root(self):
59
"""Return a write-locked state with a single root entry."""
60
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
61
root_entry_direntry = ('', '', 'a-root-value'), [
62
('d', '', 0, False, packed_stat),
65
dirblocks.append(('', [root_entry_direntry]))
66
dirblocks.append(('', []))
67
state = self.create_empty_dirstate()
69
state._set_data([], dirblocks)
76
def create_dirstate_with_root_and_subdir(self):
77
"""Return a locked DirState with a root and a subdir"""
78
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
79
subdir_entry = ('', 'subdir', 'subdir-id'), [
80
('d', '', 0, False, packed_stat),
82
state = self.create_dirstate_with_root()
84
dirblocks = list(state._dirblocks)
85
dirblocks[1][1].append(subdir_entry)
86
state._set_data([], dirblocks)
92
def create_complex_dirstate(self):
93
"""This dirstate contains multiple files and directories.
103
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
105
Notice that a/e is an empty directory.
107
:return: The dirstate, still write-locked.
109
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
110
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
111
root_entry = ('', '', 'a-root-value'), [
112
('d', '', 0, False, packed_stat),
114
a_entry = ('', 'a', 'a-dir'), [
115
('d', '', 0, False, packed_stat),
117
b_entry = ('', 'b', 'b-dir'), [
118
('d', '', 0, False, packed_stat),
120
c_entry = ('', 'c', 'c-file'), [
121
('f', null_sha, 10, False, packed_stat),
123
d_entry = ('', 'd', 'd-file'), [
124
('f', null_sha, 20, False, packed_stat),
126
e_entry = ('a', 'e', 'e-dir'), [
127
('d', '', 0, False, packed_stat),
129
f_entry = ('a', 'f', 'f-file'), [
130
('f', null_sha, 30, False, packed_stat),
132
g_entry = ('b', 'g', 'g-file'), [
133
('f', null_sha, 30, False, packed_stat),
135
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
136
('f', null_sha, 40, False, packed_stat),
139
dirblocks.append(('', [root_entry]))
140
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
141
dirblocks.append(('a', [e_entry, f_entry]))
142
dirblocks.append(('b', [g_entry, h_entry]))
143
state = dirstate.DirState.initialize('dirstate')
146
state._set_data([], dirblocks)
152
def check_state_with_reopen(self, expected_result, state):
153
"""Check that state has current state expected_result.
155
This will check the current state, open the file anew and check it
157
This function expects the current state to be locked for writing, and
158
will unlock it before re-opening.
159
This is required because we can't open a lock_read() while something
160
else has a lock_write().
161
write => mutually exclusive lock
164
# The state should already be write locked, since we just had to do
165
# some operation to get here.
166
assert state._lock_token is not None
168
self.assertEqual(expected_result[0], state.get_parent_ids())
169
# there should be no ghosts in this tree.
170
self.assertEqual([], state.get_ghosts())
171
# there should be one fileid in this tree - the root of the tree.
172
self.assertEqual(expected_result[1], list(state._iter_entries()))
177
state = dirstate.DirState.on_file('dirstate')
180
self.assertEqual(expected_result[1], list(state._iter_entries()))
184
def create_basic_dirstate(self):
185
"""Create a dirstate with a few files and directories.
195
tree = self.make_branch_and_tree('tree')
196
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
197
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
198
self.build_tree(['tree/' + p for p in paths])
199
tree.set_root_id('TREE_ROOT')
200
tree.add([p.rstrip('/') for p in paths], file_ids)
201
tree.commit('initial', rev_id='rev-1')
202
revision_id = 'rev-1'
203
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
204
t = self.get_transport('tree')
205
a_text = t.get_bytes('a')
206
a_sha = osutils.sha_string(a_text)
208
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
209
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
210
c_text = t.get_bytes('b/c')
211
c_sha = osutils.sha_string(c_text)
213
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
214
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
215
e_text = t.get_bytes('b/d/e')
216
e_sha = osutils.sha_string(e_text)
218
b_c_text = t.get_bytes('b-c')
219
b_c_sha = osutils.sha_string(b_c_text)
220
b_c_len = len(b_c_text)
221
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
222
f_text = t.get_bytes('f')
223
f_sha = osutils.sha_string(f_text)
225
null_stat = dirstate.DirState.NULLSTAT
227
'':(('', '', 'TREE_ROOT'), [
228
('d', '', 0, False, null_stat),
229
('d', '', 0, False, revision_id),
231
'a':(('', 'a', 'a-id'), [
232
('f', '', 0, False, null_stat),
233
('f', a_sha, a_len, False, revision_id),
235
'b':(('', 'b', 'b-id'), [
236
('d', '', 0, False, null_stat),
237
('d', '', 0, False, revision_id),
239
'b/c':(('b', 'c', 'c-id'), [
240
('f', '', 0, False, null_stat),
241
('f', c_sha, c_len, False, revision_id),
243
'b/d':(('b', 'd', 'd-id'), [
244
('d', '', 0, False, null_stat),
245
('d', '', 0, False, revision_id),
247
'b/d/e':(('b/d', 'e', 'e-id'), [
248
('f', '', 0, False, null_stat),
249
('f', e_sha, e_len, False, revision_id),
251
'b-c':(('', 'b-c', 'b-c-id'), [
252
('f', '', 0, False, null_stat),
253
('f', b_c_sha, b_c_len, False, revision_id),
255
'f':(('', 'f', 'f-id'), [
256
('f', '', 0, False, null_stat),
257
('f', f_sha, f_len, False, revision_id),
260
state = dirstate.DirState.from_tree(tree, 'dirstate')
265
# Use a different object, to make sure nothing is pre-cached in memory.
266
state = dirstate.DirState.on_file('dirstate')
268
self.addCleanup(state.unlock)
269
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
270
state._dirblock_state)
271
# This is code is only really tested if we actually have to make more
272
# than one read, so set the page size to something smaller.
273
# We want it to contain about 2.2 records, so that we have a couple
274
# records that we can read per attempt
275
state._bisect_page_size = 200
276
return tree, state, expected
278
def create_duplicated_dirstate(self):
279
"""Create a dirstate with a deleted and added entries.
281
This grabs a basic_dirstate, and then removes and re adds every entry
284
tree, state, expected = self.create_basic_dirstate()
285
# Now we will just remove and add every file so we get an extra entry
286
# per entry. Unversion in reverse order so we handle subdirs
287
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
288
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
289
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
291
# Update the expected dictionary.
292
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
293
orig = expected[path]
295
# This record was deleted in the current tree
296
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
298
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
299
# And didn't exist in the basis tree
300
expected[path2] = (new_key, [orig[1][0],
301
dirstate.DirState.NULL_PARENT_DETAILS])
303
# We will replace the 'dirstate' file underneath 'state', but that is
304
# okay as lock as we unlock 'state' first.
307
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
313
# But we need to leave state in a read-lock because we already have
314
# a cleanup scheduled
316
return tree, state, expected
318
def create_renamed_dirstate(self):
319
"""Create a dirstate with a few internal renames.
321
This takes the basic dirstate, and moves the paths around.
323
tree, state, expected = self.create_basic_dirstate()
325
tree.rename_one('a', 'b/g')
327
tree.rename_one('b/d', 'h')
329
old_a = expected['a']
330
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
331
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
332
('r', 'a', 0, False, '')])
333
old_d = expected['b/d']
334
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
335
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
336
('r', 'b/d', 0, False, '')])
338
old_e = expected['b/d/e']
339
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
341
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
342
('r', 'b/d/e', 0, False, '')])
346
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
353
return tree, state, expected
356
class TestTreeToDirState(TestCaseWithDirState):
358
def test_empty_to_dirstate(self):
359
"""We should be able to create a dirstate for an empty tree."""
360
# There are no files on disk and no parents
361
tree = self.make_branch_and_tree('tree')
362
expected_result = ([], [
363
(('', '', tree.path2id('')), # common details
364
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
366
state = dirstate.DirState.from_tree(tree, 'dirstate')
368
self.check_state_with_reopen(expected_result, state)
370
def test_1_parents_empty_to_dirstate(self):
371
# create a parent by doing a commit
372
tree = self.make_branch_and_tree('tree')
373
rev_id = tree.commit('first post').encode('utf8')
374
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
375
expected_result = ([rev_id], [
376
(('', '', tree.path2id('')), # common details
377
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
378
('d', '', 0, False, rev_id), # first parent details
380
state = dirstate.DirState.from_tree(tree, 'dirstate')
381
self.check_state_with_reopen(expected_result, state)
388
def test_2_parents_empty_to_dirstate(self):
389
# create a parent by doing a commit
390
tree = self.make_branch_and_tree('tree')
391
rev_id = tree.commit('first post')
392
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
393
rev_id2 = tree2.commit('second post', allow_pointless=True)
394
tree.merge_from_branch(tree2.branch)
395
expected_result = ([rev_id, rev_id2], [
396
(('', '', tree.path2id('')), # common details
397
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
398
('d', '', 0, False, rev_id), # first parent details
399
('d', '', 0, False, rev_id2), # second parent details
401
state = dirstate.DirState.from_tree(tree, 'dirstate')
402
self.check_state_with_reopen(expected_result, state)
409
def test_empty_unknowns_are_ignored_to_dirstate(self):
410
"""We should be able to create a dirstate for an empty tree."""
411
# There are no files on disk and no parents
412
tree = self.make_branch_and_tree('tree')
413
self.build_tree(['tree/unknown'])
414
expected_result = ([], [
415
(('', '', tree.path2id('')), # common details
416
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
418
state = dirstate.DirState.from_tree(tree, 'dirstate')
419
self.check_state_with_reopen(expected_result, state)
421
def get_tree_with_a_file(self):
422
tree = self.make_branch_and_tree('tree')
423
self.build_tree(['tree/a file'])
424
tree.add('a file', 'a file id')
427
def test_non_empty_no_parents_to_dirstate(self):
428
"""We should be able to create a dirstate for an empty tree."""
429
# There are files on disk and no parents
430
tree = self.get_tree_with_a_file()
431
expected_result = ([], [
432
(('', '', tree.path2id('')), # common details
433
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
435
(('', 'a file', 'a file id'), # common
436
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
439
state = dirstate.DirState.from_tree(tree, 'dirstate')
440
self.check_state_with_reopen(expected_result, state)
442
def test_1_parents_not_empty_to_dirstate(self):
443
# create a parent by doing a commit
444
tree = self.get_tree_with_a_file()
445
rev_id = tree.commit('first post').encode('utf8')
446
# change the current content to be different this will alter stat, sha
448
self.build_tree_contents([('tree/a file', 'new content\n')])
449
expected_result = ([rev_id], [
450
(('', '', tree.path2id('')), # common details
451
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
452
('d', '', 0, False, rev_id), # first parent details
454
(('', 'a file', 'a file id'), # common
455
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
456
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
457
rev_id), # first parent
460
state = dirstate.DirState.from_tree(tree, 'dirstate')
461
self.check_state_with_reopen(expected_result, state)
463
def test_2_parents_not_empty_to_dirstate(self):
464
# create a parent by doing a commit
465
tree = self.get_tree_with_a_file()
466
rev_id = tree.commit('first post').encode('utf8')
467
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
468
# change the current content to be different this will alter stat, sha
470
self.build_tree_contents([('tree2/a file', 'merge content\n')])
471
rev_id2 = tree2.commit('second post').encode('utf8')
472
tree.merge_from_branch(tree2.branch)
473
# change the current content to be different this will alter stat, sha
474
# and length again, giving us three distinct values:
475
self.build_tree_contents([('tree/a file', 'new content\n')])
476
expected_result = ([rev_id, rev_id2], [
477
(('', '', tree.path2id('')), # common details
478
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
479
('d', '', 0, False, rev_id), # first parent details
480
('d', '', 0, False, rev_id2), # second parent details
482
(('', 'a file', 'a file id'), # common
483
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
484
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
485
rev_id), # first parent
486
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
487
rev_id2), # second parent
490
state = dirstate.DirState.from_tree(tree, 'dirstate')
491
self.check_state_with_reopen(expected_result, state)
493
def test_colliding_fileids(self):
494
# test insertion of parents creating several entries at the same path.
495
# we used to have a bug where they could cause the dirstate to break
496
# its ordering invariants.
497
# create some trees to test from
500
tree = self.make_branch_and_tree('tree%d' % i)
501
self.build_tree(['tree%d/name' % i,])
502
tree.add(['name'], ['file-id%d' % i])
503
revision_id = 'revid-%d' % i
504
tree.commit('message', rev_id=revision_id)
505
parents.append((revision_id,
506
tree.branch.repository.revision_tree(revision_id)))
507
# now fold these trees into a dirstate
508
state = dirstate.DirState.initialize('dirstate')
510
state.set_parent_trees(parents, [])
516
class TestDirStateOnFile(TestCaseWithDirState):
518
def test_construct_with_path(self):
519
tree = self.make_branch_and_tree('tree')
520
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
521
# we want to be able to get the lines of the dirstate that we will
523
lines = state.get_lines()
525
self.build_tree_contents([('dirstate', ''.join(lines))])
527
# no parents, default tree content
528
expected_result = ([], [
529
(('', '', tree.path2id('')), # common details
530
# current tree details, but new from_tree skips statting, it
531
# uses set_state_from_inventory, and thus depends on the
533
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
536
state = dirstate.DirState.on_file('dirstate')
537
state.lock_write() # check_state_with_reopen will save() and unlock it
538
self.check_state_with_reopen(expected_result, state)
540
def test_can_save_clean_on_file(self):
541
tree = self.make_branch_and_tree('tree')
542
state = dirstate.DirState.from_tree(tree, 'dirstate')
544
# doing a save should work here as there have been no changes.
546
# TODO: stat it and check it hasn't changed; may require waiting
547
# for the state accuracy window.
551
def test_can_save_in_read_lock(self):
552
self.build_tree(['a-file'])
553
state = dirstate.DirState.initialize('dirstate')
555
# No stat and no sha1 sum.
556
state.add('a-file', 'a-file-id', 'file', None, '')
561
# Now open in readonly mode
562
state = dirstate.DirState.on_file('dirstate')
565
entry = state._get_entry(0, path_utf8='a-file')
566
# The current sha1 sum should be empty
567
self.assertEqual('', entry[1][0][1])
568
# We should have a real entry.
569
self.assertNotEqual((None, None), entry)
570
# Make sure everything is old enough
571
state._sha_cutoff_time()
572
state._cutoff_time += 10
573
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
574
# We should have gotten a real sha1
575
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
578
# The dirblock has been updated
579
self.assertEqual(sha1sum, entry[1][0][1])
580
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
581
state._dirblock_state)
584
# Now, since we are the only one holding a lock, we should be able
585
# to save and have it written to disk
590
# Re-open the file, and ensure that the state has been updated.
591
state = dirstate.DirState.on_file('dirstate')
594
entry = state._get_entry(0, path_utf8='a-file')
595
self.assertEqual(sha1sum, entry[1][0][1])
599
def test_save_fails_quietly_if_locked(self):
600
"""If dirstate is locked, save will fail without complaining."""
601
self.build_tree(['a-file'])
602
state = dirstate.DirState.initialize('dirstate')
604
# No stat and no sha1 sum.
605
state.add('a-file', 'a-file-id', 'file', None, '')
610
state = dirstate.DirState.on_file('dirstate')
613
entry = state._get_entry(0, path_utf8='a-file')
614
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
615
# We should have gotten a real sha1
616
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
618
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
619
state._dirblock_state)
621
# Now, before we try to save, grab another dirstate, and take out a
623
# TODO: jam 20070315 Ideally this would be locked by another
624
# process. To make sure the file is really OS locked.
625
state2 = dirstate.DirState.on_file('dirstate')
628
# This won't actually write anything, because it couldn't grab
629
# a write lock. But it shouldn't raise an error, either.
630
# TODO: jam 20070315 We should probably distinguish between
631
# being dirty because of 'update_entry'. And dirty
632
# because of real modification. So that save() *does*
633
# raise a real error if it fails when we have real
641
# The file on disk should not be modified.
642
state = dirstate.DirState.on_file('dirstate')
645
entry = state._get_entry(0, path_utf8='a-file')
646
self.assertEqual('', entry[1][0][1])
651
class TestDirStateInitialize(TestCaseWithDirState):
653
def test_initialize(self):
654
expected_result = ([], [
655
(('', '', 'TREE_ROOT'), # common details
656
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
659
state = dirstate.DirState.initialize('dirstate')
661
self.assertIsInstance(state, dirstate.DirState)
662
lines = state.get_lines()
665
# On win32 you can't read from a locked file, even within the same
666
# process. So we have to unlock and release before we check the file
668
self.assertFileEqual(''.join(lines), 'dirstate')
669
state.lock_read() # check_state_with_reopen will unlock
670
self.check_state_with_reopen(expected_result, state)
673
class TestDirStateManipulations(TestCaseWithDirState):
675
def test_set_state_from_inventory_no_content_no_parents(self):
676
# setting the current inventory is a slow but important api to support.
677
tree1 = self.make_branch_and_memory_tree('tree1')
681
revid1 = tree1.commit('foo').encode('utf8')
682
root_id = tree1.inventory.root.file_id
683
inv = tree1.inventory
686
expected_result = [], [
687
(('', '', root_id), [
688
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
689
state = dirstate.DirState.initialize('dirstate')
691
state.set_state_from_inventory(inv)
692
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
694
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
695
state._dirblock_state)
700
# This will unlock it
701
self.check_state_with_reopen(expected_result, state)
703
def test_set_state_from_inventory_preserves_hashcache(self):
704
# https://bugs.launchpad.net/bzr/+bug/146176
705
# set_state_from_inventory should preserve the stat and hash value for
706
# workingtree files that are not changed by the inventory.
708
tree = self.make_branch_and_tree('.')
709
# depends on the default format using dirstate...
712
# make a dirstate with some valid hashcache data
713
# file on disk, but that's not needed for this test
714
foo_contents = 'contents of foo'
715
self.build_tree_contents([('foo', foo_contents)])
716
tree.add('foo', 'foo-id')
718
foo_stat = os.stat('foo')
719
foo_packed = dirstate.pack_stat(foo_stat)
720
foo_sha = osutils.sha_string(foo_contents)
721
foo_size = len(foo_contents)
723
# should not be cached yet, because the file's too fresh
725
(('', 'foo', 'foo-id',),
726
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
727
tree._dirstate._get_entry(0, 'foo-id'))
728
# poke in some hashcache information - it wouldn't normally be
729
# stored because it's too fresh
730
tree._dirstate.update_minimal(
731
('', 'foo', 'foo-id'),
732
'f', False, foo_sha, foo_packed, foo_size, 'foo')
733
# now should be cached
735
(('', 'foo', 'foo-id',),
736
[('f', foo_sha, foo_size, False, foo_packed)]),
737
tree._dirstate._get_entry(0, 'foo-id'))
739
# extract the inventory, and add something to it
740
inv = tree._get_inventory()
741
# should see the file we poked in...
742
self.assertTrue(inv.has_id('foo-id'))
743
self.assertTrue(inv.has_filename('foo'))
744
inv.add_path('bar', 'file', 'bar-id')
745
tree._dirstate._validate()
746
# this used to cause it to lose its hashcache
747
tree._dirstate.set_state_from_inventory(inv)
748
tree._dirstate._validate()
754
# now check that the state still has the original hashcache value
755
state = tree._dirstate
757
foo_tuple = state._get_entry(0, path_utf8='foo')
759
(('', 'foo', 'foo-id',),
760
[('f', foo_sha, len(foo_contents), False,
761
dirstate.pack_stat(foo_stat))]),
767
def test_set_state_from_inventory_mixed_paths(self):
768
tree1 = self.make_branch_and_tree('tree1')
769
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
770
'tree1/a/b/foo', 'tree1/a-b/bar'])
773
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
774
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
775
tree1.commit('rev1', rev_id='rev1')
776
root_id = tree1.get_root_id()
777
inv = tree1.inventory
780
expected_result1 = [('', '', root_id, 'd'),
781
('', 'a', 'a-id', 'd'),
782
('', 'a-b', 'a-b-id', 'd'),
783
('a', 'b', 'b-id', 'd'),
784
('a/b', 'foo', 'foo-id', 'f'),
785
('a-b', 'bar', 'bar-id', 'f'),
787
expected_result2 = [('', '', root_id, 'd'),
788
('', 'a', 'a-id', 'd'),
789
('', 'a-b', 'a-b-id', 'd'),
790
('a-b', 'bar', 'bar-id', 'f'),
792
state = dirstate.DirState.initialize('dirstate')
794
state.set_state_from_inventory(inv)
796
for entry in state._iter_entries():
797
values.append(entry[0] + entry[1][0][:1])
798
self.assertEqual(expected_result1, values)
800
state.set_state_from_inventory(inv)
802
for entry in state._iter_entries():
803
values.append(entry[0] + entry[1][0][:1])
804
self.assertEqual(expected_result2, values)
808
def test_set_path_id_no_parents(self):
809
"""The id of a path can be changed trivally with no parents."""
810
state = dirstate.DirState.initialize('dirstate')
812
# check precondition to be sure the state does change appropriately.
814
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
815
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
816
list(state._iter_entries()))
817
state.set_path_id('', 'foobarbaz')
819
(('', '', 'foobarbaz'), [('d', '', 0, False,
820
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
821
self.assertEqual(expected_rows, list(state._iter_entries()))
822
# should work across save too
826
state = dirstate.DirState.on_file('dirstate')
830
self.assertEqual(expected_rows, list(state._iter_entries()))
834
def test_set_path_id_with_parents(self):
835
"""Set the root file id in a dirstate with parents"""
836
mt = self.make_branch_and_tree('mt')
837
# in case the default tree format uses a different root id
838
mt.set_root_id('TREE_ROOT')
839
mt.commit('foo', rev_id='parent-revid')
840
rt = mt.branch.repository.revision_tree('parent-revid')
841
state = dirstate.DirState.initialize('dirstate')
844
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
845
state.set_path_id('', 'foobarbaz')
847
# now see that it is what we expected
849
(('', '', 'TREE_ROOT'),
850
[('a', '', 0, False, ''),
851
('d', '', 0, False, 'parent-revid'),
853
(('', '', 'foobarbaz'),
854
[('d', '', 0, False, ''),
855
('a', '', 0, False, ''),
859
self.assertEqual(expected_rows, list(state._iter_entries()))
860
# should work across save too
864
# now flush & check we get the same
865
state = dirstate.DirState.on_file('dirstate')
869
self.assertEqual(expected_rows, list(state._iter_entries()))
872
# now change within an existing file-backed state
876
state.set_path_id('', 'tree-root-2')
882
def test_set_parent_trees_no_content(self):
883
# set_parent_trees is a slow but important api to support.
884
tree1 = self.make_branch_and_memory_tree('tree1')
888
revid1 = tree1.commit('foo')
891
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
892
tree2 = MemoryTree.create_on_branch(branch2)
895
revid2 = tree2.commit('foo')
896
root_id = tree2.inventory.root.file_id
899
state = dirstate.DirState.initialize('dirstate')
901
state.set_path_id('', root_id)
902
state.set_parent_trees(
903
((revid1, tree1.branch.repository.revision_tree(revid1)),
904
(revid2, tree2.branch.repository.revision_tree(revid2)),
905
('ghost-rev', None)),
907
# check we can reopen and use the dirstate after setting parent
914
state = dirstate.DirState.on_file('dirstate')
917
self.assertEqual([revid1, revid2, 'ghost-rev'],
918
state.get_parent_ids())
919
# iterating the entire state ensures that the state is parsable.
920
list(state._iter_entries())
921
# be sure that it sets not appends - change it
922
state.set_parent_trees(
923
((revid1, tree1.branch.repository.revision_tree(revid1)),
924
('ghost-rev', None)),
926
# and now put it back.
927
state.set_parent_trees(
928
((revid1, tree1.branch.repository.revision_tree(revid1)),
929
(revid2, tree2.branch.repository.revision_tree(revid2)),
930
('ghost-rev', tree2.branch.repository.revision_tree(None))),
932
self.assertEqual([revid1, revid2, 'ghost-rev'],
933
state.get_parent_ids())
934
# the ghost should be recorded as such by set_parent_trees.
935
self.assertEqual(['ghost-rev'], state.get_ghosts())
937
[(('', '', root_id), [
938
('d', '', 0, False, dirstate.DirState.NULLSTAT),
939
('d', '', 0, False, revid1),
940
('d', '', 0, False, revid2)
942
list(state._iter_entries()))
946
def test_set_parent_trees_file_missing_from_tree(self):
947
# Adding a parent tree may reference files not in the current state.
948
# they should get listed just once by id, even if they are in two
950
# set_parent_trees is a slow but important api to support.
951
tree1 = self.make_branch_and_memory_tree('tree1')
955
tree1.add(['a file'], ['file-id'], ['file'])
956
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
957
revid1 = tree1.commit('foo')
960
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
961
tree2 = MemoryTree.create_on_branch(branch2)
964
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
965
revid2 = tree2.commit('foo')
966
root_id = tree2.inventory.root.file_id
969
# check the layout in memory
970
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
971
(('', '', root_id), [
972
('d', '', 0, False, dirstate.DirState.NULLSTAT),
973
('d', '', 0, False, revid1.encode('utf8')),
974
('d', '', 0, False, revid2.encode('utf8'))
976
(('', 'a file', 'file-id'), [
977
('a', '', 0, False, ''),
978
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
979
revid1.encode('utf8')),
980
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
981
revid2.encode('utf8'))
984
state = dirstate.DirState.initialize('dirstate')
986
state.set_path_id('', root_id)
987
state.set_parent_trees(
988
((revid1, tree1.branch.repository.revision_tree(revid1)),
989
(revid2, tree2.branch.repository.revision_tree(revid2)),
995
# check_state_with_reopen will unlock
996
self.check_state_with_reopen(expected_result, state)
998
### add a path via _set_data - so we dont need delta work, just
999
# raw data in, and ensure that it comes out via get_lines happily.
1001
def test_add_path_to_root_no_parents_all_data(self):
1002
# The most trivial addition of a path is when there are no parents and
1003
# its in the root and all data about the file is supplied
1004
self.build_tree(['a file'])
1005
stat = os.lstat('a file')
1006
# the 1*20 is the sha1 pretend value.
1007
state = dirstate.DirState.initialize('dirstate')
1008
expected_entries = [
1009
(('', '', 'TREE_ROOT'), [
1010
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1012
(('', 'a file', 'a file id'), [
1013
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1017
state.add('a file', 'a file id', 'file', stat, '1'*20)
1018
# having added it, it should be in the output of iter_entries.
1019
self.assertEqual(expected_entries, list(state._iter_entries()))
1020
# saving and reloading should not affect this.
1024
state = dirstate.DirState.on_file('dirstate')
1027
self.assertEqual(expected_entries, list(state._iter_entries()))
1031
def test_add_path_to_unversioned_directory(self):
1032
"""Adding a path to an unversioned directory should error.
1034
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1035
once dirstate is stable and if it is merged with WorkingTree3, consider
1036
removing this copy of the test.
1038
self.build_tree(['unversioned/', 'unversioned/a file'])
1039
state = dirstate.DirState.initialize('dirstate')
1041
self.assertRaises(errors.NotVersionedError, state.add,
1042
'unversioned/a file', 'a file id', 'file', None, None)
1046
def test_add_directory_to_root_no_parents_all_data(self):
1047
# The most trivial addition of a dir is when there are no parents and
1048
# its in the root and all data about the file is supplied
1049
self.build_tree(['a dir/'])
1050
stat = os.lstat('a dir')
1051
expected_entries = [
1052
(('', '', 'TREE_ROOT'), [
1053
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1055
(('', 'a dir', 'a dir id'), [
1056
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1059
state = dirstate.DirState.initialize('dirstate')
1061
state.add('a dir', 'a dir id', 'directory', stat, None)
1062
# having added it, it should be in the output of iter_entries.
1063
self.assertEqual(expected_entries, list(state._iter_entries()))
1064
# saving and reloading should not affect this.
1068
state = dirstate.DirState.on_file('dirstate')
1072
self.assertEqual(expected_entries, list(state._iter_entries()))
1076
def test_add_symlink_to_root_no_parents_all_data(self):
1077
# The most trivial addition of a symlink when there are no parents and
1078
# its in the root and all data about the file is supplied
1079
# bzr doesn't support fake symlinks on windows, yet.
1080
if not has_symlinks():
1081
raise TestSkipped("No symlink support")
1082
os.symlink('target', 'a link')
1083
stat = os.lstat('a link')
1084
expected_entries = [
1085
(('', '', 'TREE_ROOT'), [
1086
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1088
(('', 'a link', 'a link id'), [
1089
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1092
state = dirstate.DirState.initialize('dirstate')
1094
state.add('a link', 'a link id', 'symlink', stat, 'target')
1095
# having added it, it should be in the output of iter_entries.
1096
self.assertEqual(expected_entries, list(state._iter_entries()))
1097
# saving and reloading should not affect this.
1101
state = dirstate.DirState.on_file('dirstate')
1104
self.assertEqual(expected_entries, list(state._iter_entries()))
1108
def test_add_directory_and_child_no_parents_all_data(self):
1109
# after adding a directory, we should be able to add children to it.
1110
self.build_tree(['a dir/', 'a dir/a file'])
1111
dirstat = os.lstat('a dir')
1112
filestat = os.lstat('a dir/a file')
1113
expected_entries = [
1114
(('', '', 'TREE_ROOT'), [
1115
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1117
(('', 'a dir', 'a dir id'), [
1118
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1120
(('a dir', 'a file', 'a file id'), [
1121
('f', '1'*20, 25, False,
1122
dirstate.pack_stat(filestat)), # current tree details
1125
state = dirstate.DirState.initialize('dirstate')
1127
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1128
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
1129
# added it, it should be in the output of iter_entries.
1130
self.assertEqual(expected_entries, list(state._iter_entries()))
1131
# saving and reloading should not affect this.
1135
state = dirstate.DirState.on_file('dirstate')
1138
self.assertEqual(expected_entries, list(state._iter_entries()))
1142
def test_add_tree_reference(self):
1143
# make a dirstate and add a tree reference
1144
state = dirstate.DirState.initialize('dirstate')
1146
('', 'subdir', 'subdir-id'),
1147
[('t', 'subtree-123123', 0, False,
1148
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1151
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1152
entry = state._get_entry(0, 'subdir-id', 'subdir')
1153
self.assertEqual(entry, expected_entry)
1158
# now check we can read it back
1162
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1163
self.assertEqual(entry, entry2)
1164
self.assertEqual(entry, expected_entry)
1165
# and lookup by id should work too
1166
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1167
self.assertEqual(entry, expected_entry)
1171
def test_add_forbidden_names(self):
1172
state = dirstate.DirState.initialize('dirstate')
1173
self.addCleanup(state.unlock)
1174
self.assertRaises(errors.BzrError,
1175
state.add, '.', 'ass-id', 'directory', None, None)
1176
self.assertRaises(errors.BzrError,
1177
state.add, '..', 'ass-id', 'directory', None, None)
1180
class TestGetLines(TestCaseWithDirState):
1182
def test_get_line_with_2_rows(self):
1183
state = self.create_dirstate_with_root_and_subdir()
1185
self.assertEqual(['#bazaar dirstate flat format 3\n',
1186
'crc32: 41262208\n',
1190
'\x00\x00a-root-value\x00'
1191
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1192
'\x00subdir\x00subdir-id\x00'
1193
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1194
], state.get_lines())
1198
def test_entry_to_line(self):
1199
state = self.create_dirstate_with_root()
1202
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1203
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1204
state._entry_to_line(state._dirblocks[0][1][0]))
1208
def test_entry_to_line_with_parent(self):
1209
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1210
root_entry = ('', '', 'a-root-value'), [
1211
('d', '', 0, False, packed_stat), # current tree details
1212
# first: a pointer to the current location
1213
('a', 'dirname/basename', 0, False, ''),
1215
state = dirstate.DirState.initialize('dirstate')
1218
'\x00\x00a-root-value\x00'
1219
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1220
'a\x00dirname/basename\x000\x00n\x00',
1221
state._entry_to_line(root_entry))
1225
def test_entry_to_line_with_two_parents_at_different_paths(self):
1226
# / in the tree, at / in one parent and /dirname/basename in the other.
1227
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1228
root_entry = ('', '', 'a-root-value'), [
1229
('d', '', 0, False, packed_stat), # current tree details
1230
('d', '', 0, False, 'rev_id'), # first parent details
1231
# second: a pointer to the current location
1232
('a', 'dirname/basename', 0, False, ''),
1234
state = dirstate.DirState.initialize('dirstate')
1237
'\x00\x00a-root-value\x00'
1238
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1239
'd\x00\x000\x00n\x00rev_id\x00'
1240
'a\x00dirname/basename\x000\x00n\x00',
1241
state._entry_to_line(root_entry))
1245
def test_iter_entries(self):
1246
# we should be able to iterate the dirstate entries from end to end
1247
# this is for get_lines to be easy to read.
1248
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1250
root_entries = [(('', '', 'a-root-value'), [
1251
('d', '', 0, False, packed_stat), # current tree details
1253
dirblocks.append(('', root_entries))
1254
# add two files in the root
1255
subdir_entry = ('', 'subdir', 'subdir-id'), [
1256
('d', '', 0, False, packed_stat), # current tree details
1258
afile_entry = ('', 'afile', 'afile-id'), [
1259
('f', 'sha1value', 34, False, packed_stat), # current tree details
1261
dirblocks.append(('', [subdir_entry, afile_entry]))
1263
file_entry2 = ('subdir', '2file', '2file-id'), [
1264
('f', 'sha1value', 23, False, packed_stat), # current tree details
1266
dirblocks.append(('subdir', [file_entry2]))
1267
state = dirstate.DirState.initialize('dirstate')
1269
state._set_data([], dirblocks)
1270
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1272
self.assertEqual(expected_entries, list(state._iter_entries()))
1277
class TestGetBlockRowIndex(TestCaseWithDirState):
1279
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1280
file_present, state, dirname, basename, tree_index):
1281
self.assertEqual((block_index, row_index, dir_present, file_present),
1282
state._get_block_entry_index(dirname, basename, tree_index))
1284
block = state._dirblocks[block_index]
1285
self.assertEqual(dirname, block[0])
1286
if dir_present and file_present:
1287
row = state._dirblocks[block_index][1][row_index]
1288
self.assertEqual(dirname, row[0][0])
1289
self.assertEqual(basename, row[0][1])
1291
def test_simple_structure(self):
1292
state = self.create_dirstate_with_root_and_subdir()
1293
self.addCleanup(state.unlock)
1294
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1295
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1296
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1297
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1298
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1301
def test_complex_structure_exists(self):
1302
state = self.create_complex_dirstate()
1303
self.addCleanup(state.unlock)
1304
# Make sure we can find everything that exists
1305
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1306
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1307
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1308
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1309
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1310
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1311
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1312
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1313
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1314
'b', 'h\xc3\xa5', 0)
1316
def test_complex_structure_missing(self):
1317
state = self.create_complex_dirstate()
1318
self.addCleanup(state.unlock)
1319
# Make sure things would be inserted in the right locations
1320
# '_' comes before 'a'
1321
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1322
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1323
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1324
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1326
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1327
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1328
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1329
# This would be inserted between a/ and b/
1330
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1332
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1335
class TestGetEntry(TestCaseWithDirState):
1337
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1338
"""Check that the right entry is returned for a request to getEntry."""
1339
entry = state._get_entry(index, path_utf8=path)
1341
self.assertEqual((None, None), entry)
1344
self.assertEqual((dirname, basename, file_id), cur[:3])
1346
def test_simple_structure(self):
1347
state = self.create_dirstate_with_root_and_subdir()
1348
self.addCleanup(state.unlock)
1349
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1350
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1351
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1352
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1353
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1355
def test_complex_structure_exists(self):
1356
state = self.create_complex_dirstate()
1357
self.addCleanup(state.unlock)
1358
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1359
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1360
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1361
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1362
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1363
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1364
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1365
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1366
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1369
def test_complex_structure_missing(self):
1370
state = self.create_complex_dirstate()
1371
self.addCleanup(state.unlock)
1372
self.assertEntryEqual(None, None, None, state, '_', 0)
1373
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1374
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1375
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1377
def test_get_entry_uninitialized(self):
1378
"""Calling get_entry will load data if it needs to"""
1379
state = self.create_dirstate_with_root()
1385
state = dirstate.DirState.on_file('dirstate')
1388
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1389
state._header_state)
1390
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1391
state._dirblock_state)
1392
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1397
class TestIterChildEntries(TestCaseWithDirState):
1399
def create_dirstate_with_two_trees(self):
1400
"""This dirstate contains multiple files and directories.
1410
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1412
Notice that a/e is an empty directory.
1414
There is one parent tree, which has the same shape with the following variations:
1415
b/g in the parent is gone.
1416
b/h in the parent has a different id
1417
b/i is new in the parent
1418
c is renamed to b/j in the parent
1420
:return: The dirstate, still write-locked.
1422
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1423
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1424
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1425
root_entry = ('', '', 'a-root-value'), [
1426
('d', '', 0, False, packed_stat),
1427
('d', '', 0, False, 'parent-revid'),
1429
a_entry = ('', 'a', 'a-dir'), [
1430
('d', '', 0, False, packed_stat),
1431
('d', '', 0, False, 'parent-revid'),
1433
b_entry = ('', 'b', 'b-dir'), [
1434
('d', '', 0, False, packed_stat),
1435
('d', '', 0, False, 'parent-revid'),
1437
c_entry = ('', 'c', 'c-file'), [
1438
('f', null_sha, 10, False, packed_stat),
1439
('r', 'b/j', 0, False, ''),
1441
d_entry = ('', 'd', 'd-file'), [
1442
('f', null_sha, 20, False, packed_stat),
1443
('f', 'd', 20, False, 'parent-revid'),
1445
e_entry = ('a', 'e', 'e-dir'), [
1446
('d', '', 0, False, packed_stat),
1447
('d', '', 0, False, 'parent-revid'),
1449
f_entry = ('a', 'f', 'f-file'), [
1450
('f', null_sha, 30, False, packed_stat),
1451
('f', 'f', 20, False, 'parent-revid'),
1453
g_entry = ('b', 'g', 'g-file'), [
1454
('f', null_sha, 30, False, packed_stat),
1455
NULL_PARENT_DETAILS,
1457
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1458
('f', null_sha, 40, False, packed_stat),
1459
NULL_PARENT_DETAILS,
1461
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1462
NULL_PARENT_DETAILS,
1463
('f', 'h', 20, False, 'parent-revid'),
1465
i_entry = ('b', 'i', 'i-file'), [
1466
NULL_PARENT_DETAILS,
1467
('f', 'h', 20, False, 'parent-revid'),
1469
j_entry = ('b', 'j', 'c-file'), [
1470
('r', 'c', 0, False, ''),
1471
('f', 'j', 20, False, 'parent-revid'),
1474
dirblocks.append(('', [root_entry]))
1475
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1476
dirblocks.append(('a', [e_entry, f_entry]))
1477
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1478
state = dirstate.DirState.initialize('dirstate')
1481
state._set_data(['parent'], dirblocks)
1485
return state, dirblocks
1487
def test_iter_children_b(self):
1488
state, dirblocks = self.create_dirstate_with_two_trees()
1489
self.addCleanup(state.unlock)
1490
expected_result = []
1491
expected_result.append(dirblocks[3][1][2]) # h2
1492
expected_result.append(dirblocks[3][1][3]) # i
1493
expected_result.append(dirblocks[3][1][4]) # j
1494
self.assertEqual(expected_result,
1495
list(state._iter_child_entries(1, 'b')))
1498
class TestDirstateSortOrder(TestCaseWithTransport):
1499
"""Test that DirState adds entries in the right order."""
1501
def test_add_sorting(self):
1502
"""Add entries in lexicographical order, we get path sorted order.
1504
This tests it to a depth of 4, to make sure we don't just get it right
1505
at a single depth. 'a/a' should come before 'a-a', even though it
1506
doesn't lexicographically.
1508
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1509
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1512
state = dirstate.DirState.initialize('dirstate')
1513
self.addCleanup(state.unlock)
1515
fake_stat = os.stat('dirstate')
1517
d_id = d.replace('/', '_')+'-id'
1518
file_path = d + '/f'
1519
file_id = file_path.replace('/', '_')+'-id'
1520
state.add(d, d_id, 'directory', fake_stat, null_sha)
1521
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1523
expected = ['', '', 'a',
1524
'a/a', 'a/a/a', 'a/a/a/a',
1525
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1527
split = lambda p:p.split('/')
1528
self.assertEqual(sorted(expected, key=split), expected)
1529
dirblock_names = [d[0] for d in state._dirblocks]
1530
self.assertEqual(expected, dirblock_names)
1532
def test_set_parent_trees_correct_order(self):
1533
"""After calling set_parent_trees() we should maintain the order."""
1534
dirs = ['a', 'a-a', 'a/a']
1536
state = dirstate.DirState.initialize('dirstate')
1537
self.addCleanup(state.unlock)
1539
fake_stat = os.stat('dirstate')
1541
d_id = d.replace('/', '_')+'-id'
1542
file_path = d + '/f'
1543
file_id = file_path.replace('/', '_')+'-id'
1544
state.add(d, d_id, 'directory', fake_stat, null_sha)
1545
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1547
expected = ['', '', 'a', 'a/a', 'a-a']
1548
dirblock_names = [d[0] for d in state._dirblocks]
1549
self.assertEqual(expected, dirblock_names)
1551
# *really* cheesy way to just get an empty tree
1552
repo = self.make_repository('repo')
1553
empty_tree = repo.revision_tree(None)
1554
state.set_parent_trees([('null:', empty_tree)], [])
1556
dirblock_names = [d[0] for d in state._dirblocks]
1557
self.assertEqual(expected, dirblock_names)
1560
class InstrumentedDirState(dirstate.DirState):
1561
"""An DirState with instrumented sha1 functionality."""
1563
def __init__(self, path):
1564
super(InstrumentedDirState, self).__init__(path)
1565
self._time_offset = 0
1567
# member is dynamically set in DirState.__init__ to turn on trace
1568
self._sha1_file = self._sha1_file_and_log
1570
def _sha_cutoff_time(self):
1571
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1572
self._cutoff_time = timestamp + self._time_offset
1574
def _sha1_file_and_log(self, abspath):
1575
self._log.append(('sha1', abspath))
1576
return osutils.sha_file_by_name(abspath)
1578
def _read_link(self, abspath, old_link):
1579
self._log.append(('read_link', abspath, old_link))
1580
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1582
def _lstat(self, abspath, entry):
1583
self._log.append(('lstat', abspath))
1584
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1586
def _is_executable(self, mode, old_executable):
1587
self._log.append(('is_exec', mode, old_executable))
1588
return super(InstrumentedDirState, self)._is_executable(mode,
1591
def adjust_time(self, secs):
1592
"""Move the clock forward or back.
1594
:param secs: The amount to adjust the clock by. Positive values make it
1595
seem as if we are in the future, negative values make it seem like we
1598
self._time_offset += secs
1599
self._cutoff_time = None
1602
class _FakeStat(object):
1603
"""A class with the same attributes as a real stat result."""
1605
def __init__(self, size, mtime, ctime, dev, ino, mode):
1607
self.st_mtime = mtime
1608
self.st_ctime = ctime
1614
class TestUpdateEntry(TestCaseWithDirState):
1615
"""Test the DirState.update_entry functions"""
1617
def get_state_with_a(self):
1618
"""Create a DirState tracking a single object named 'a'"""
1619
state = InstrumentedDirState.initialize('dirstate')
1620
self.addCleanup(state.unlock)
1621
state.add('a', 'a-id', 'file', None, '')
1622
entry = state._get_entry(0, path_utf8='a')
1625
def test_update_entry(self):
1626
state, entry = self.get_state_with_a()
1627
self.build_tree(['a'])
1628
# Add one where we don't provide the stat or sha already
1629
self.assertEqual(('', 'a', 'a-id'), entry[0])
1630
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1632
# Flush the buffers to disk
1634
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1635
state._dirblock_state)
1637
stat_value = os.lstat('a')
1638
packed_stat = dirstate.pack_stat(stat_value)
1639
link_or_sha1 = state.update_entry(entry, abspath='a',
1640
stat_value=stat_value)
1641
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1644
# The dirblock entry should not cache the file's sha1
1645
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1647
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1648
state._dirblock_state)
1649
mode = stat_value.st_mode
1650
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1653
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1654
state._dirblock_state)
1656
# If we do it again right away, we don't know if the file has changed
1657
# so we will re-read the file. Roll the clock back so the file is
1658
# guaranteed to look too new.
1659
state.adjust_time(-10)
1661
link_or_sha1 = state.update_entry(entry, abspath='a',
1662
stat_value=stat_value)
1663
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1664
('sha1', 'a'), ('is_exec', mode, False),
1666
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1668
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1669
state._dirblock_state)
1670
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1674
# However, if we move the clock forward so the file is considered
1675
# "stable", it should just cache the value.
1676
state.adjust_time(+20)
1677
link_or_sha1 = state.update_entry(entry, abspath='a',
1678
stat_value=stat_value)
1679
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1681
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1682
('sha1', 'a'), ('is_exec', mode, False),
1683
('sha1', 'a'), ('is_exec', mode, False),
1685
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1688
# Subsequent calls will just return the cached value
1689
link_or_sha1 = state.update_entry(entry, abspath='a',
1690
stat_value=stat_value)
1691
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1693
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1694
('sha1', 'a'), ('is_exec', mode, False),
1695
('sha1', 'a'), ('is_exec', mode, False),
1697
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1700
def test_update_entry_symlink(self):
1701
"""Update entry should read symlinks."""
1702
if not osutils.has_symlinks():
1703
# PlatformDeficiency / TestSkipped
1704
raise TestSkipped("No symlink support")
1705
state, entry = self.get_state_with_a()
1707
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1708
state._dirblock_state)
1709
os.symlink('target', 'a')
1711
state.adjust_time(-10) # Make the symlink look new
1712
stat_value = os.lstat('a')
1713
packed_stat = dirstate.pack_stat(stat_value)
1714
link_or_sha1 = state.update_entry(entry, abspath='a',
1715
stat_value=stat_value)
1716
self.assertEqual('target', link_or_sha1)
1717
self.assertEqual([('read_link', 'a', '')], state._log)
1718
# Dirblock is not updated (the link is too new)
1719
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1721
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1722
state._dirblock_state)
1724
# Because the stat_value looks new, we should re-read the target
1725
link_or_sha1 = state.update_entry(entry, abspath='a',
1726
stat_value=stat_value)
1727
self.assertEqual('target', link_or_sha1)
1728
self.assertEqual([('read_link', 'a', ''),
1729
('read_link', 'a', ''),
1731
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1733
state.adjust_time(+20) # Skip into the future, all files look old
1734
link_or_sha1 = state.update_entry(entry, abspath='a',
1735
stat_value=stat_value)
1736
self.assertEqual('target', link_or_sha1)
1737
# We need to re-read the link because only now can we cache it
1738
self.assertEqual([('read_link', 'a', ''),
1739
('read_link', 'a', ''),
1740
('read_link', 'a', ''),
1742
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1745
# Another call won't re-read the link
1746
self.assertEqual([('read_link', 'a', ''),
1747
('read_link', 'a', ''),
1748
('read_link', 'a', ''),
1750
link_or_sha1 = state.update_entry(entry, abspath='a',
1751
stat_value=stat_value)
1752
self.assertEqual('target', link_or_sha1)
1753
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1756
def do_update_entry(self, state, entry, abspath):
1757
stat_value = os.lstat(abspath)
1758
return state.update_entry(entry, abspath, stat_value)
1760
def test_update_entry_dir(self):
1761
state, entry = self.get_state_with_a()
1762
self.build_tree(['a/'])
1763
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1765
def test_update_entry_dir_unchanged(self):
1766
state, entry = self.get_state_with_a()
1767
self.build_tree(['a/'])
1768
state.adjust_time(+20)
1769
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1770
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1771
state._dirblock_state)
1773
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1774
state._dirblock_state)
1775
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1776
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1777
state._dirblock_state)
1779
def test_update_entry_file_unchanged(self):
1780
state, entry = self.get_state_with_a()
1781
self.build_tree(['a'])
1782
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1783
state.adjust_time(+20)
1784
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1785
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1786
state._dirblock_state)
1788
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1789
state._dirblock_state)
1790
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1791
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1792
state._dirblock_state)
1794
def create_and_test_file(self, state, entry):
1795
"""Create a file at 'a' and verify the state finds it.
1797
The state should already be versioning *something* at 'a'. This makes
1798
sure that state.update_entry recognizes it as a file.
1800
self.build_tree(['a'])
1801
stat_value = os.lstat('a')
1802
packed_stat = dirstate.pack_stat(stat_value)
1804
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1805
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1807
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1811
def create_and_test_dir(self, state, entry):
1812
"""Create a directory at 'a' and verify the state finds it.
1814
The state should already be versioning *something* at 'a'. This makes
1815
sure that state.update_entry recognizes it as a directory.
1817
self.build_tree(['a/'])
1818
stat_value = os.lstat('a')
1819
packed_stat = dirstate.pack_stat(stat_value)
1821
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1822
self.assertIs(None, link_or_sha1)
1823
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1827
def create_and_test_symlink(self, state, entry):
1828
"""Create a symlink at 'a' and verify the state finds it.
1830
The state should already be versioning *something* at 'a'. This makes
1831
sure that state.update_entry recognizes it as a symlink.
1833
This should not be called if this platform does not have symlink
1836
# caller should care about skipping test on platforms without symlinks
1837
os.symlink('path/to/foo', 'a')
1839
stat_value = os.lstat('a')
1840
packed_stat = dirstate.pack_stat(stat_value)
1842
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1843
self.assertEqual('path/to/foo', link_or_sha1)
1844
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1848
def test_update_file_to_dir(self):
1849
"""If a file changes to a directory we return None for the sha.
1850
We also update the inventory record.
1852
state, entry = self.get_state_with_a()
1853
# The file sha1 won't be cached unless the file is old
1854
state.adjust_time(+10)
1855
self.create_and_test_file(state, entry)
1857
self.create_and_test_dir(state, entry)
1859
def test_update_file_to_symlink(self):
1860
"""File becomes a symlink"""
1861
if not osutils.has_symlinks():
1862
# PlatformDeficiency / TestSkipped
1863
raise TestSkipped("No symlink support")
1864
state, entry = self.get_state_with_a()
1865
# The file sha1 won't be cached unless the file is old
1866
state.adjust_time(+10)
1867
self.create_and_test_file(state, entry)
1869
self.create_and_test_symlink(state, entry)
1871
def test_update_dir_to_file(self):
1872
"""Directory becoming a file updates the entry."""
1873
state, entry = self.get_state_with_a()
1874
# The file sha1 won't be cached unless the file is old
1875
state.adjust_time(+10)
1876
self.create_and_test_dir(state, entry)
1878
self.create_and_test_file(state, entry)
1880
def test_update_dir_to_symlink(self):
1881
"""Directory becomes a symlink"""
1882
if not osutils.has_symlinks():
1883
# PlatformDeficiency / TestSkipped
1884
raise TestSkipped("No symlink support")
1885
state, entry = self.get_state_with_a()
1886
# The symlink target won't be cached if it isn't old
1887
state.adjust_time(+10)
1888
self.create_and_test_dir(state, entry)
1890
self.create_and_test_symlink(state, entry)
1892
def test_update_symlink_to_file(self):
1893
"""Symlink becomes a file"""
1894
if not has_symlinks():
1895
raise TestSkipped("No symlink support")
1896
state, entry = self.get_state_with_a()
1897
# The symlink and file info won't be cached unless old
1898
state.adjust_time(+10)
1899
self.create_and_test_symlink(state, entry)
1901
self.create_and_test_file(state, entry)
1903
def test_update_symlink_to_dir(self):
1904
"""Symlink becomes a directory"""
1905
if not has_symlinks():
1906
raise TestSkipped("No symlink support")
1907
state, entry = self.get_state_with_a()
1908
# The symlink target won't be cached if it isn't old
1909
state.adjust_time(+10)
1910
self.create_and_test_symlink(state, entry)
1912
self.create_and_test_dir(state, entry)
1914
def test__is_executable_win32(self):
1915
state, entry = self.get_state_with_a()
1916
self.build_tree(['a'])
1918
# Make sure we are using the win32 implementation of _is_executable
1919
state._is_executable = state._is_executable_win32
1921
# The file on disk is not executable, but we are marking it as though
1922
# it is. With _is_executable_win32 we ignore what is on disk.
1923
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1925
stat_value = os.lstat('a')
1926
packed_stat = dirstate.pack_stat(stat_value)
1928
state.adjust_time(-10) # Make sure everything is new
1929
state.update_entry(entry, abspath='a', stat_value=stat_value)
1931
# The row is updated, but the executable bit stays set.
1932
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1935
# Make the disk object look old enough to cache
1936
state.adjust_time(+20)
1937
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1938
state.update_entry(entry, abspath='a', stat_value=stat_value)
1939
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1942
class TestPackStat(TestCaseWithTransport):
1944
def assertPackStat(self, expected, stat_value):
1945
"""Check the packed and serialized form of a stat value."""
1946
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1948
def test_pack_stat_int(self):
1949
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1950
# Make sure that all parameters have an impact on the packed stat.
1951
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1954
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1955
st.st_mtime = 1172758620
1957
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1958
st.st_ctime = 1172758630
1960
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1963
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1964
st.st_ino = 6499540L
1966
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1967
st.st_mode = 0100744
1969
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1971
def test_pack_stat_float(self):
1972
"""On some platforms mtime and ctime are floats.
1974
Make sure we don't get warnings or errors, and that we ignore changes <
1977
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1978
777L, 6499538L, 0100644)
1979
# These should all be the same as the integer counterparts
1980
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1981
st.st_mtime = 1172758620.0
1983
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1984
st.st_ctime = 1172758630.0
1986
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1987
# fractional seconds are discarded, so no change from above
1988
st.st_mtime = 1172758620.453
1989
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1990
st.st_ctime = 1172758630.228
1991
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1994
class TestBisect(TestCaseWithDirState):
1995
"""Test the ability to bisect into the disk format."""
1997
def assertBisect(self, expected_map, map_keys, state, paths):
1998
"""Assert that bisecting for paths returns the right result.
2000
:param expected_map: A map from key => entry value
2001
:param map_keys: The keys to expect for each path
2002
:param state: The DirState object.
2003
:param paths: A list of paths, these will automatically be split into
2004
(dir, name) tuples, and sorted according to how _bisect
2007
result = state._bisect(paths)
2008
# For now, results are just returned in whatever order we read them.
2009
# We could sort by (dir, name, file_id) or something like that, but in
2010
# the end it would still be fairly arbitrary, and we don't want the
2011
# extra overhead if we can avoid it. So sort everything to make sure
2013
assert len(map_keys) == len(paths)
2015
for path, keys in zip(paths, map_keys):
2017
# This should not be present in the output
2019
expected[path] = sorted(expected_map[k] for k in keys)
2021
# The returned values are just arranged randomly based on when they
2022
# were read, for testing, make sure it is properly sorted.
2026
self.assertEqual(expected, result)
2028
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
2029
"""Assert that bisecting for dirbblocks returns the right result.
2031
:param expected_map: A map from key => expected values
2032
:param map_keys: A nested list of paths we expect to be returned.
2033
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
2034
:param state: The DirState object.
2035
:param paths: A list of directories
2037
result = state._bisect_dirblocks(paths)
2038
assert len(map_keys) == len(paths)
2041
for path, keys in zip(paths, map_keys):
2043
# This should not be present in the output
2045
expected[path] = sorted(expected_map[k] for k in keys)
2049
self.assertEqual(expected, result)
2051
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
2052
"""Assert the return value of a recursive bisection.
2054
:param expected_map: A map from key => entry value
2055
:param map_keys: A list of paths we expect to be returned.
2056
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
2057
:param state: The DirState object.
2058
:param paths: A list of files and directories. It will be broken up
2059
into (dir, name) pairs and sorted before calling _bisect_recursive.
2062
for key in map_keys:
2063
entry = expected_map[key]
2064
dir_name_id, trees_info = entry
2065
expected[dir_name_id] = trees_info
2067
result = state._bisect_recursive(paths)
2069
self.assertEqual(expected, result)
2071
def test_bisect_each(self):
2072
"""Find a single record using bisect."""
2073
tree, state, expected = self.create_basic_dirstate()
2075
# Bisect should return the rows for the specified files.
2076
self.assertBisect(expected, [['']], state, [''])
2077
self.assertBisect(expected, [['a']], state, ['a'])
2078
self.assertBisect(expected, [['b']], state, ['b'])
2079
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2080
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2081
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2082
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2083
self.assertBisect(expected, [['f']], state, ['f'])
2085
def test_bisect_multi(self):
2086
"""Bisect can be used to find multiple records at the same time."""
2087
tree, state, expected = self.create_basic_dirstate()
2088
# Bisect should be capable of finding multiple entries at the same time
2089
self.assertBisect(expected, [['a'], ['b'], ['f']],
2090
state, ['a', 'b', 'f'])
2091
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2092
state, ['f', 'b/d', 'b/d/e'])
2093
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2094
state, ['b', 'b-c', 'b/c'])
2096
def test_bisect_one_page(self):
2097
"""Test bisect when there is only 1 page to read"""
2098
tree, state, expected = self.create_basic_dirstate()
2099
state._bisect_page_size = 5000
2100
self.assertBisect(expected,[['']], state, [''])
2101
self.assertBisect(expected,[['a']], state, ['a'])
2102
self.assertBisect(expected,[['b']], state, ['b'])
2103
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2104
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2105
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2106
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2107
self.assertBisect(expected,[['f']], state, ['f'])
2108
self.assertBisect(expected,[['a'], ['b'], ['f']],
2109
state, ['a', 'b', 'f'])
2110
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2111
state, ['b/d', 'b/d/e', 'f'])
2112
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2113
state, ['b', 'b/c', 'b-c'])
2115
def test_bisect_duplicate_paths(self):
2116
"""When bisecting for a path, handle multiple entries."""
2117
tree, state, expected = self.create_duplicated_dirstate()
2119
# Now make sure that both records are properly returned.
2120
self.assertBisect(expected, [['']], state, [''])
2121
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2122
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2123
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2124
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2125
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2127
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2128
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2130
def test_bisect_page_size_too_small(self):
2131
"""If the page size is too small, we will auto increase it."""
2132
tree, state, expected = self.create_basic_dirstate()
2133
state._bisect_page_size = 50
2134
self.assertBisect(expected, [None], state, ['b/e'])
2135
self.assertBisect(expected, [['a']], state, ['a'])
2136
self.assertBisect(expected, [['b']], state, ['b'])
2137
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2138
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2139
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2140
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2141
self.assertBisect(expected, [['f']], state, ['f'])
2143
def test_bisect_missing(self):
2144
"""Test that bisect return None if it cannot find a path."""
2145
tree, state, expected = self.create_basic_dirstate()
2146
self.assertBisect(expected, [None], state, ['foo'])
2147
self.assertBisect(expected, [None], state, ['b/foo'])
2148
self.assertBisect(expected, [None], state, ['bar/foo'])
2149
self.assertBisect(expected, [None], state, ['b-c/foo'])
2151
self.assertBisect(expected, [['a'], None, ['b/d']],
2152
state, ['a', 'foo', 'b/d'])
2154
def test_bisect_rename(self):
2155
"""Check that we find a renamed row."""
2156
tree, state, expected = self.create_renamed_dirstate()
2158
# Search for the pre and post renamed entries
2159
self.assertBisect(expected, [['a']], state, ['a'])
2160
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2161
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2162
self.assertBisect(expected, [['h']], state, ['h'])
2164
# What about b/d/e? shouldn't that also get 2 directory entries?
2165
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2166
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2168
def test_bisect_dirblocks(self):
2169
tree, state, expected = self.create_duplicated_dirstate()
2170
self.assertBisectDirBlocks(expected,
2171
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2173
self.assertBisectDirBlocks(expected,
2174
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2175
self.assertBisectDirBlocks(expected,
2176
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2177
self.assertBisectDirBlocks(expected,
2178
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2179
['b/c', 'b/c2', 'b/d', 'b/d2'],
2180
['b/d/e', 'b/d/e2'],
2181
], state, ['', 'b', 'b/d'])
2183
def test_bisect_dirblocks_missing(self):
2184
tree, state, expected = self.create_basic_dirstate()
2185
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2186
state, ['b/d', 'b/e'])
2187
# Files don't show up in this search
2188
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2189
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2190
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2191
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2192
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2194
def test_bisect_recursive_each(self):
2195
tree, state, expected = self.create_basic_dirstate()
2196
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2197
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2198
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2199
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2200
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2202
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2204
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2208
def test_bisect_recursive_multiple(self):
2209
tree, state, expected = self.create_basic_dirstate()
2210
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2211
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2212
state, ['b/d', 'b/d/e'])
2214
def test_bisect_recursive_missing(self):
2215
tree, state, expected = self.create_basic_dirstate()
2216
self.assertBisectRecursive(expected, [], state, ['d'])
2217
self.assertBisectRecursive(expected, [], state, ['b/e'])
2218
self.assertBisectRecursive(expected, [], state, ['g'])
2219
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2221
def test_bisect_recursive_renamed(self):
2222
tree, state, expected = self.create_renamed_dirstate()
2224
# Looking for either renamed item should find the other
2225
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2226
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2227
# Looking in the containing directory should find the rename target,
2228
# and anything in a subdir of the renamed target.
2229
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2230
'b/d/e', 'b/g', 'h', 'h/e'],
2234
class TestDirstateValidation(TestCaseWithDirState):
2236
def test_validate_correct_dirstate(self):
2237
state = self.create_complex_dirstate()
2240
# and make sure we can also validate with a read lock
2247
def test_dirblock_not_sorted(self):
2248
tree, state, expected = self.create_renamed_dirstate()
2249
state._read_dirblocks_if_needed()
2250
last_dirblock = state._dirblocks[-1]
2251
# we're appending to the dirblock, but this name comes before some of
2252
# the existing names; that's wrong
2253
last_dirblock[1].append(
2254
(('h', 'aaaa', 'a-id'),
2255
[('a', '', 0, False, ''),
2256
('a', '', 0, False, '')]))
2257
e = self.assertRaises(AssertionError,
2259
self.assertContainsRe(str(e), 'not sorted')
2261
def test_dirblock_name_mismatch(self):
2262
tree, state, expected = self.create_renamed_dirstate()
2263
state._read_dirblocks_if_needed()
2264
last_dirblock = state._dirblocks[-1]
2265
# add an entry with the wrong directory name
2266
last_dirblock[1].append(
2268
[('a', '', 0, False, ''),
2269
('a', '', 0, False, '')]))
2270
e = self.assertRaises(AssertionError,
2272
self.assertContainsRe(str(e),
2273
"doesn't match directory name")
2275
def test_dirblock_missing_rename(self):
2276
tree, state, expected = self.create_renamed_dirstate()
2277
state._read_dirblocks_if_needed()
2278
last_dirblock = state._dirblocks[-1]
2279
# make another entry for a-id, without a correct 'r' pointer to
2280
# the real occurrence in the working tree
2281
last_dirblock[1].append(
2282
(('h', 'z', 'a-id'),
2283
[('a', '', 0, False, ''),
2284
('a', '', 0, False, '')]))
2285
e = self.assertRaises(AssertionError,
2287
self.assertContainsRe(str(e),
2288
'file a-id is absent in row')
2291
class TestDirstateTreeReference(TestCaseWithDirState):
2293
def test_reference_revision_is_none(self):
2294
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2295
subtree = self.make_branch_and_tree('tree/subtree',
2296
format='dirstate-with-subtree')
2297
subtree.set_root_id('subtree')
2298
tree.add_reference(subtree)
2300
state = dirstate.DirState.from_tree(tree, 'dirstate')
2301
key = ('', 'subtree', 'subtree')
2302
expected = ('', [(key,
2303
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2306
self.assertEqual(expected, state._find_block(key))