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.tests import (
32
TestCaseWithTransport,
38
# general checks for NOT_IN_MEMORY error conditions.
39
# set_path_id on a NOT_IN_MEMORY dirstate
40
# set_path_id unicode support
41
# set_path_id setting id of a path not root
42
# set_path_id setting id when there are parents without the id in the parents
43
# set_path_id setting id when there are parents with the id in the parents
44
# set_path_id setting id when state is not in memory
45
# set_path_id setting id when state is in memory unmodified
46
# set_path_id setting id when state is in memory modified
49
class TestCaseWithDirState(TestCaseWithTransport):
50
"""Helper functions for creating DirState objects with various content."""
52
def create_empty_dirstate(self):
53
"""Return a locked but empty dirstate"""
54
state = dirstate.DirState.initialize('dirstate')
57
def create_dirstate_with_root(self):
58
"""Return a write-locked state with a single root entry."""
59
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
60
root_entry_direntry = ('', '', 'a-root-value'), [
61
('d', '', 0, False, packed_stat),
64
dirblocks.append(('', [root_entry_direntry]))
65
dirblocks.append(('', []))
66
state = self.create_empty_dirstate()
68
state._set_data([], dirblocks)
75
def create_dirstate_with_root_and_subdir(self):
76
"""Return a locked DirState with a root and a subdir"""
77
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
78
subdir_entry = ('', 'subdir', 'subdir-id'), [
79
('d', '', 0, False, packed_stat),
81
state = self.create_dirstate_with_root()
83
dirblocks = list(state._dirblocks)
84
dirblocks[1][1].append(subdir_entry)
85
state._set_data([], dirblocks)
91
def create_complex_dirstate(self):
92
"""This dirstate contains multiple files and directories.
102
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
104
Notice that a/e is an empty directory.
106
:return: The dirstate, still write-locked.
108
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
109
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
110
root_entry = ('', '', 'a-root-value'), [
111
('d', '', 0, False, packed_stat),
113
a_entry = ('', 'a', 'a-dir'), [
114
('d', '', 0, False, packed_stat),
116
b_entry = ('', 'b', 'b-dir'), [
117
('d', '', 0, False, packed_stat),
119
c_entry = ('', 'c', 'c-file'), [
120
('f', null_sha, 10, False, packed_stat),
122
d_entry = ('', 'd', 'd-file'), [
123
('f', null_sha, 20, False, packed_stat),
125
e_entry = ('a', 'e', 'e-dir'), [
126
('d', '', 0, False, packed_stat),
128
f_entry = ('a', 'f', 'f-file'), [
129
('f', null_sha, 30, False, packed_stat),
131
g_entry = ('b', 'g', 'g-file'), [
132
('f', null_sha, 30, False, packed_stat),
134
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
135
('f', null_sha, 40, False, packed_stat),
138
dirblocks.append(('', [root_entry]))
139
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
140
dirblocks.append(('a', [e_entry, f_entry]))
141
dirblocks.append(('b', [g_entry, h_entry]))
142
state = dirstate.DirState.initialize('dirstate')
145
state._set_data([], dirblocks)
151
def check_state_with_reopen(self, expected_result, state):
152
"""Check that state has current state expected_result.
154
This will check the current state, open the file anew and check it
156
This function expects the current state to be locked for writing, and
157
will unlock it before re-opening.
158
This is required because we can't open a lock_read() while something
159
else has a lock_write().
160
write => mutually exclusive lock
163
# The state should already be write locked, since we just had to do
164
# some operation to get here.
165
assert state._lock_token is not None
167
self.assertEqual(expected_result[0], state.get_parent_ids())
168
# there should be no ghosts in this tree.
169
self.assertEqual([], state.get_ghosts())
170
# there should be one fileid in this tree - the root of the tree.
171
self.assertEqual(expected_result[1], list(state._iter_entries()))
176
state = dirstate.DirState.on_file('dirstate')
179
self.assertEqual(expected_result[1], list(state._iter_entries()))
183
def create_basic_dirstate(self):
184
"""Create a dirstate with a few files and directories.
194
tree = self.make_branch_and_tree('tree')
195
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
196
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
197
self.build_tree(['tree/' + p for p in paths])
198
tree.set_root_id('TREE_ROOT')
199
tree.add([p.rstrip('/') for p in paths], file_ids)
200
tree.commit('initial', rev_id='rev-1')
201
revision_id = 'rev-1'
202
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
203
t = self.get_transport('tree')
204
a_text = t.get_bytes('a')
205
a_sha = osutils.sha_string(a_text)
207
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
208
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
209
c_text = t.get_bytes('b/c')
210
c_sha = osutils.sha_string(c_text)
212
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
213
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
214
e_text = t.get_bytes('b/d/e')
215
e_sha = osutils.sha_string(e_text)
217
b_c_text = t.get_bytes('b-c')
218
b_c_sha = osutils.sha_string(b_c_text)
219
b_c_len = len(b_c_text)
220
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
221
f_text = t.get_bytes('f')
222
f_sha = osutils.sha_string(f_text)
224
null_stat = dirstate.DirState.NULLSTAT
226
'':(('', '', 'TREE_ROOT'), [
227
('d', '', 0, False, null_stat),
228
('d', '', 0, False, revision_id),
230
'a':(('', 'a', 'a-id'), [
231
('f', '', 0, False, null_stat),
232
('f', a_sha, a_len, False, revision_id),
234
'b':(('', 'b', 'b-id'), [
235
('d', '', 0, False, null_stat),
236
('d', '', 0, False, revision_id),
238
'b/c':(('b', 'c', 'c-id'), [
239
('f', '', 0, False, null_stat),
240
('f', c_sha, c_len, False, revision_id),
242
'b/d':(('b', 'd', 'd-id'), [
243
('d', '', 0, False, null_stat),
244
('d', '', 0, False, revision_id),
246
'b/d/e':(('b/d', 'e', 'e-id'), [
247
('f', '', 0, False, null_stat),
248
('f', e_sha, e_len, False, revision_id),
250
'b-c':(('', 'b-c', 'b-c-id'), [
251
('f', '', 0, False, null_stat),
252
('f', b_c_sha, b_c_len, False, revision_id),
254
'f':(('', 'f', 'f-id'), [
255
('f', '', 0, False, null_stat),
256
('f', f_sha, f_len, False, revision_id),
259
state = dirstate.DirState.from_tree(tree, 'dirstate')
264
# Use a different object, to make sure nothing is pre-cached in memory.
265
state = dirstate.DirState.on_file('dirstate')
267
self.addCleanup(state.unlock)
268
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
269
state._dirblock_state)
270
# This is code is only really tested if we actually have to make more
271
# than one read, so set the page size to something smaller.
272
# We want it to contain about 2.2 records, so that we have a couple
273
# records that we can read per attempt
274
state._bisect_page_size = 200
275
return tree, state, expected
277
def create_duplicated_dirstate(self):
278
"""Create a dirstate with a deleted and added entries.
280
This grabs a basic_dirstate, and then removes and re adds every entry
283
tree, state, expected = self.create_basic_dirstate()
284
# Now we will just remove and add every file so we get an extra entry
285
# per entry. Unversion in reverse order so we handle subdirs
286
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
287
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
288
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
290
# Update the expected dictionary.
291
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
292
orig = expected[path]
294
# This record was deleted in the current tree
295
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
297
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
298
# And didn't exist in the basis tree
299
expected[path2] = (new_key, [orig[1][0],
300
dirstate.DirState.NULL_PARENT_DETAILS])
302
# We will replace the 'dirstate' file underneath 'state', but that is
303
# okay as lock as we unlock 'state' first.
306
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
312
# But we need to leave state in a read-lock because we already have
313
# a cleanup scheduled
315
return tree, state, expected
317
def create_renamed_dirstate(self):
318
"""Create a dirstate with a few internal renames.
320
This takes the basic dirstate, and moves the paths around.
322
tree, state, expected = self.create_basic_dirstate()
324
tree.rename_one('a', 'b/g')
326
tree.rename_one('b/d', 'h')
328
old_a = expected['a']
329
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
330
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
331
('r', 'a', 0, False, '')])
332
old_d = expected['b/d']
333
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
334
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
335
('r', 'b/d', 0, False, '')])
337
old_e = expected['b/d/e']
338
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
340
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
341
('r', 'b/d/e', 0, False, '')])
345
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
352
return tree, state, expected
355
class TestTreeToDirState(TestCaseWithDirState):
357
def test_empty_to_dirstate(self):
358
"""We should be able to create a dirstate for an empty tree."""
359
# There are no files on disk and no parents
360
tree = self.make_branch_and_tree('tree')
361
expected_result = ([], [
362
(('', '', tree.path2id('')), # common details
363
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
365
state = dirstate.DirState.from_tree(tree, 'dirstate')
367
self.check_state_with_reopen(expected_result, state)
369
def test_1_parents_empty_to_dirstate(self):
370
# create a parent by doing a commit
371
tree = self.make_branch_and_tree('tree')
372
rev_id = tree.commit('first post').encode('utf8')
373
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
374
expected_result = ([rev_id], [
375
(('', '', tree.path2id('')), # common details
376
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
377
('d', '', 0, False, rev_id), # first parent details
379
state = dirstate.DirState.from_tree(tree, 'dirstate')
380
self.check_state_with_reopen(expected_result, state)
387
def test_2_parents_empty_to_dirstate(self):
388
# create a parent by doing a commit
389
tree = self.make_branch_and_tree('tree')
390
rev_id = tree.commit('first post')
391
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
392
rev_id2 = tree2.commit('second post', allow_pointless=True)
393
tree.merge_from_branch(tree2.branch)
394
expected_result = ([rev_id, rev_id2], [
395
(('', '', tree.path2id('')), # common details
396
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
397
('d', '', 0, False, rev_id), # first parent details
398
('d', '', 0, False, rev_id2), # second parent details
400
state = dirstate.DirState.from_tree(tree, 'dirstate')
401
self.check_state_with_reopen(expected_result, state)
408
def test_empty_unknowns_are_ignored_to_dirstate(self):
409
"""We should be able to create a dirstate for an empty tree."""
410
# There are no files on disk and no parents
411
tree = self.make_branch_and_tree('tree')
412
self.build_tree(['tree/unknown'])
413
expected_result = ([], [
414
(('', '', tree.path2id('')), # common details
415
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
417
state = dirstate.DirState.from_tree(tree, 'dirstate')
418
self.check_state_with_reopen(expected_result, state)
420
def get_tree_with_a_file(self):
421
tree = self.make_branch_and_tree('tree')
422
self.build_tree(['tree/a file'])
423
tree.add('a file', 'a file id')
426
def test_non_empty_no_parents_to_dirstate(self):
427
"""We should be able to create a dirstate for an empty tree."""
428
# There are files on disk and no parents
429
tree = self.get_tree_with_a_file()
430
expected_result = ([], [
431
(('', '', tree.path2id('')), # common details
432
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
434
(('', 'a file', 'a file id'), # common
435
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
438
state = dirstate.DirState.from_tree(tree, 'dirstate')
439
self.check_state_with_reopen(expected_result, state)
441
def test_1_parents_not_empty_to_dirstate(self):
442
# create a parent by doing a commit
443
tree = self.get_tree_with_a_file()
444
rev_id = tree.commit('first post').encode('utf8')
445
# change the current content to be different this will alter stat, sha
447
self.build_tree_contents([('tree/a file', 'new content\n')])
448
expected_result = ([rev_id], [
449
(('', '', tree.path2id('')), # common details
450
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
451
('d', '', 0, False, rev_id), # first parent details
453
(('', 'a file', 'a file id'), # common
454
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
455
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
456
rev_id), # first parent
459
state = dirstate.DirState.from_tree(tree, 'dirstate')
460
self.check_state_with_reopen(expected_result, state)
462
def test_2_parents_not_empty_to_dirstate(self):
463
# create a parent by doing a commit
464
tree = self.get_tree_with_a_file()
465
rev_id = tree.commit('first post').encode('utf8')
466
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
467
# change the current content to be different this will alter stat, sha
469
self.build_tree_contents([('tree2/a file', 'merge content\n')])
470
rev_id2 = tree2.commit('second post').encode('utf8')
471
tree.merge_from_branch(tree2.branch)
472
# change the current content to be different this will alter stat, sha
473
# and length again, giving us three distinct values:
474
self.build_tree_contents([('tree/a file', 'new content\n')])
475
expected_result = ([rev_id, rev_id2], [
476
(('', '', tree.path2id('')), # common details
477
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
478
('d', '', 0, False, rev_id), # first parent details
479
('d', '', 0, False, rev_id2), # second parent details
481
(('', 'a file', 'a file id'), # common
482
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
483
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
484
rev_id), # first parent
485
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
486
rev_id2), # second parent
489
state = dirstate.DirState.from_tree(tree, 'dirstate')
490
self.check_state_with_reopen(expected_result, state)
492
def test_colliding_fileids(self):
493
# test insertion of parents creating several entries at the same path.
494
# we used to have a bug where they could cause the dirstate to break
495
# its ordering invariants.
496
# create some trees to test from
499
tree = self.make_branch_and_tree('tree%d' % i)
500
self.build_tree(['tree%d/name' % i,])
501
tree.add(['name'], ['file-id%d' % i])
502
revision_id = 'revid-%d' % i
503
tree.commit('message', rev_id=revision_id)
504
parents.append((revision_id,
505
tree.branch.repository.revision_tree(revision_id)))
506
# now fold these trees into a dirstate
507
state = dirstate.DirState.initialize('dirstate')
509
state.set_parent_trees(parents, [])
515
class TestDirStateOnFile(TestCaseWithDirState):
517
def test_construct_with_path(self):
518
tree = self.make_branch_and_tree('tree')
519
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
520
# we want to be able to get the lines of the dirstate that we will
522
lines = state.get_lines()
524
self.build_tree_contents([('dirstate', ''.join(lines))])
526
# no parents, default tree content
527
expected_result = ([], [
528
(('', '', tree.path2id('')), # common details
529
# current tree details, but new from_tree skips statting, it
530
# uses set_state_from_inventory, and thus depends on the
532
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
535
state = dirstate.DirState.on_file('dirstate')
536
state.lock_write() # check_state_with_reopen will save() and unlock it
537
self.check_state_with_reopen(expected_result, state)
539
def test_can_save_clean_on_file(self):
540
tree = self.make_branch_and_tree('tree')
541
state = dirstate.DirState.from_tree(tree, 'dirstate')
543
# doing a save should work here as there have been no changes.
545
# TODO: stat it and check it hasn't changed; may require waiting
546
# for the state accuracy window.
550
def test_can_save_in_read_lock(self):
551
self.build_tree(['a-file'])
552
state = dirstate.DirState.initialize('dirstate')
554
# No stat and no sha1 sum.
555
state.add('a-file', 'a-file-id', 'file', None, '')
560
# Now open in readonly mode
561
state = dirstate.DirState.on_file('dirstate')
564
entry = state._get_entry(0, path_utf8='a-file')
565
# The current sha1 sum should be empty
566
self.assertEqual('', entry[1][0][1])
567
# We should have a real entry.
568
self.assertNotEqual((None, None), entry)
569
# Make sure everything is old enough
570
state._sha_cutoff_time()
571
state._cutoff_time += 10
572
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
573
# We should have gotten a real sha1
574
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
577
# The dirblock has been updated
578
self.assertEqual(sha1sum, entry[1][0][1])
579
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
580
state._dirblock_state)
583
# Now, since we are the only one holding a lock, we should be able
584
# to save and have it written to disk
589
# Re-open the file, and ensure that the state has been updated.
590
state = dirstate.DirState.on_file('dirstate')
593
entry = state._get_entry(0, path_utf8='a-file')
594
self.assertEqual(sha1sum, entry[1][0][1])
598
def test_save_fails_quietly_if_locked(self):
599
"""If dirstate is locked, save will fail without complaining."""
600
self.build_tree(['a-file'])
601
state = dirstate.DirState.initialize('dirstate')
603
# No stat and no sha1 sum.
604
state.add('a-file', 'a-file-id', 'file', None, '')
609
state = dirstate.DirState.on_file('dirstate')
612
entry = state._get_entry(0, path_utf8='a-file')
613
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
614
# We should have gotten a real sha1
615
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
617
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
618
state._dirblock_state)
620
# Now, before we try to save, grab another dirstate, and take out a
622
# TODO: jam 20070315 Ideally this would be locked by another
623
# process. To make sure the file is really OS locked.
624
state2 = dirstate.DirState.on_file('dirstate')
627
# This won't actually write anything, because it couldn't grab
628
# a write lock. But it shouldn't raise an error, either.
629
# TODO: jam 20070315 We should probably distinguish between
630
# being dirty because of 'update_entry'. And dirty
631
# because of real modification. So that save() *does*
632
# raise a real error if it fails when we have real
640
# The file on disk should not be modified.
641
state = dirstate.DirState.on_file('dirstate')
644
entry = state._get_entry(0, path_utf8='a-file')
645
self.assertEqual('', entry[1][0][1])
650
class TestDirStateInitialize(TestCaseWithDirState):
652
def test_initialize(self):
653
expected_result = ([], [
654
(('', '', 'TREE_ROOT'), # common details
655
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
658
state = dirstate.DirState.initialize('dirstate')
660
self.assertIsInstance(state, dirstate.DirState)
661
lines = state.get_lines()
664
# On win32 you can't read from a locked file, even within the same
665
# process. So we have to unlock and release before we check the file
667
self.assertFileEqual(''.join(lines), 'dirstate')
668
state.lock_read() # check_state_with_reopen will unlock
669
self.check_state_with_reopen(expected_result, state)
672
class TestDirStateManipulations(TestCaseWithDirState):
674
def test_set_state_from_inventory_no_content_no_parents(self):
675
# setting the current inventory is a slow but important api to support.
676
tree1 = self.make_branch_and_memory_tree('tree1')
680
revid1 = tree1.commit('foo').encode('utf8')
681
root_id = tree1.inventory.root.file_id
682
inv = tree1.inventory
685
expected_result = [], [
686
(('', '', root_id), [
687
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
688
state = dirstate.DirState.initialize('dirstate')
690
state.set_state_from_inventory(inv)
691
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
693
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
694
state._dirblock_state)
699
# This will unlock it
700
self.check_state_with_reopen(expected_result, state)
702
def test_set_state_from_inventory_preserves_hashcache(self):
703
# https://bugs.launchpad.net/bzr/+bug/146176
704
# set_state_from_inventory should preserve the stat and hash value for
705
# workingtree files that are not changed by the inventory.
707
tree = self.make_branch_and_tree('.')
708
# depends on the default format using dirstate...
711
# make a dirstate with some valid hashcache data
712
# file on disk, but that's not needed for this test
713
foo_contents = 'contents of foo'
714
self.build_tree_contents([('foo', foo_contents)])
715
tree.add('foo', 'foo-id')
717
foo_stat = os.stat('foo')
718
foo_packed = dirstate.pack_stat(foo_stat)
719
foo_sha = osutils.sha_string(foo_contents)
720
foo_size = len(foo_contents)
722
# should not be cached yet, because the file's too fresh
724
(('', 'foo', 'foo-id',),
725
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
726
tree._dirstate._get_entry(0, 'foo-id'))
727
# poke in some hashcache information - it wouldn't normally be
728
# stored because it's too fresh
729
tree._dirstate.update_minimal(
730
('', 'foo', 'foo-id'),
731
'f', False, foo_sha, foo_packed, foo_size, 'foo')
732
# now should be cached
734
(('', 'foo', 'foo-id',),
735
[('f', foo_sha, foo_size, False, foo_packed)]),
736
tree._dirstate._get_entry(0, 'foo-id'))
738
# extract the inventory, and add something to it
739
inv = tree._get_inventory()
740
# should see the file we poked in...
741
self.assertTrue(inv.has_id('foo-id'))
742
self.assertTrue(inv.has_filename('foo'))
743
inv.add_path('bar', 'file', 'bar-id')
744
tree._dirstate._validate()
745
# this used to cause it to lose its hashcache
746
tree._dirstate.set_state_from_inventory(inv)
747
tree._dirstate._validate()
753
# now check that the state still has the original hashcache value
754
state = tree._dirstate
756
foo_tuple = state._get_entry(0, path_utf8='foo')
758
(('', 'foo', 'foo-id',),
759
[('f', foo_sha, len(foo_contents), False,
760
dirstate.pack_stat(foo_stat))]),
766
def test_set_state_from_inventory_mixed_paths(self):
767
tree1 = self.make_branch_and_tree('tree1')
768
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
769
'tree1/a/b/foo', 'tree1/a-b/bar'])
772
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
773
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
774
tree1.commit('rev1', rev_id='rev1')
775
root_id = tree1.get_root_id()
776
inv = tree1.inventory
779
expected_result1 = [('', '', root_id, 'd'),
780
('', 'a', 'a-id', 'd'),
781
('', 'a-b', 'a-b-id', 'd'),
782
('a', 'b', 'b-id', 'd'),
783
('a/b', 'foo', 'foo-id', 'f'),
784
('a-b', 'bar', 'bar-id', 'f'),
786
expected_result2 = [('', '', root_id, 'd'),
787
('', 'a', 'a-id', 'd'),
788
('', 'a-b', 'a-b-id', 'd'),
789
('a-b', 'bar', 'bar-id', 'f'),
791
state = dirstate.DirState.initialize('dirstate')
793
state.set_state_from_inventory(inv)
795
for entry in state._iter_entries():
796
values.append(entry[0] + entry[1][0][:1])
797
self.assertEqual(expected_result1, values)
799
state.set_state_from_inventory(inv)
801
for entry in state._iter_entries():
802
values.append(entry[0] + entry[1][0][:1])
803
self.assertEqual(expected_result2, values)
807
def test_set_path_id_no_parents(self):
808
"""The id of a path can be changed trivally with no parents."""
809
state = dirstate.DirState.initialize('dirstate')
811
# check precondition to be sure the state does change appropriately.
813
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
814
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
815
list(state._iter_entries()))
816
state.set_path_id('', 'foobarbaz')
818
(('', '', 'foobarbaz'), [('d', '', 0, False,
819
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
820
self.assertEqual(expected_rows, list(state._iter_entries()))
821
# should work across save too
825
state = dirstate.DirState.on_file('dirstate')
829
self.assertEqual(expected_rows, list(state._iter_entries()))
833
def test_set_path_id_with_parents(self):
834
"""Set the root file id in a dirstate with parents"""
835
mt = self.make_branch_and_tree('mt')
836
# in case the default tree format uses a different root id
837
mt.set_root_id('TREE_ROOT')
838
mt.commit('foo', rev_id='parent-revid')
839
rt = mt.branch.repository.revision_tree('parent-revid')
840
state = dirstate.DirState.initialize('dirstate')
843
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
844
state.set_path_id('', 'foobarbaz')
846
# now see that it is what we expected
848
(('', '', 'TREE_ROOT'),
849
[('a', '', 0, False, ''),
850
('d', '', 0, False, 'parent-revid'),
852
(('', '', 'foobarbaz'),
853
[('d', '', 0, False, ''),
854
('a', '', 0, False, ''),
858
self.assertEqual(expected_rows, list(state._iter_entries()))
859
# should work across save too
863
# now flush & check we get the same
864
state = dirstate.DirState.on_file('dirstate')
868
self.assertEqual(expected_rows, list(state._iter_entries()))
871
# now change within an existing file-backed state
875
state.set_path_id('', 'tree-root-2')
881
def test_set_parent_trees_no_content(self):
882
# set_parent_trees is a slow but important api to support.
883
tree1 = self.make_branch_and_memory_tree('tree1')
887
revid1 = tree1.commit('foo')
890
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
891
tree2 = MemoryTree.create_on_branch(branch2)
894
revid2 = tree2.commit('foo')
895
root_id = tree2.inventory.root.file_id
898
state = dirstate.DirState.initialize('dirstate')
900
state.set_path_id('', root_id)
901
state.set_parent_trees(
902
((revid1, tree1.branch.repository.revision_tree(revid1)),
903
(revid2, tree2.branch.repository.revision_tree(revid2)),
904
('ghost-rev', None)),
906
# check we can reopen and use the dirstate after setting parent
913
state = dirstate.DirState.on_file('dirstate')
916
self.assertEqual([revid1, revid2, 'ghost-rev'],
917
state.get_parent_ids())
918
# iterating the entire state ensures that the state is parsable.
919
list(state._iter_entries())
920
# be sure that it sets not appends - change it
921
state.set_parent_trees(
922
((revid1, tree1.branch.repository.revision_tree(revid1)),
923
('ghost-rev', None)),
925
# and now put it back.
926
state.set_parent_trees(
927
((revid1, tree1.branch.repository.revision_tree(revid1)),
928
(revid2, tree2.branch.repository.revision_tree(revid2)),
929
('ghost-rev', tree2.branch.repository.revision_tree(None))),
931
self.assertEqual([revid1, revid2, 'ghost-rev'],
932
state.get_parent_ids())
933
# the ghost should be recorded as such by set_parent_trees.
934
self.assertEqual(['ghost-rev'], state.get_ghosts())
936
[(('', '', root_id), [
937
('d', '', 0, False, dirstate.DirState.NULLSTAT),
938
('d', '', 0, False, revid1),
939
('d', '', 0, False, revid2)
941
list(state._iter_entries()))
945
def test_set_parent_trees_file_missing_from_tree(self):
946
# Adding a parent tree may reference files not in the current state.
947
# they should get listed just once by id, even if they are in two
949
# set_parent_trees is a slow but important api to support.
950
tree1 = self.make_branch_and_memory_tree('tree1')
954
tree1.add(['a file'], ['file-id'], ['file'])
955
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
956
revid1 = tree1.commit('foo')
959
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
960
tree2 = MemoryTree.create_on_branch(branch2)
963
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
964
revid2 = tree2.commit('foo')
965
root_id = tree2.inventory.root.file_id
968
# check the layout in memory
969
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
970
(('', '', root_id), [
971
('d', '', 0, False, dirstate.DirState.NULLSTAT),
972
('d', '', 0, False, revid1.encode('utf8')),
973
('d', '', 0, False, revid2.encode('utf8'))
975
(('', 'a file', 'file-id'), [
976
('a', '', 0, False, ''),
977
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
978
revid1.encode('utf8')),
979
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
980
revid2.encode('utf8'))
983
state = dirstate.DirState.initialize('dirstate')
985
state.set_path_id('', root_id)
986
state.set_parent_trees(
987
((revid1, tree1.branch.repository.revision_tree(revid1)),
988
(revid2, tree2.branch.repository.revision_tree(revid2)),
994
# check_state_with_reopen will unlock
995
self.check_state_with_reopen(expected_result, state)
997
### add a path via _set_data - so we dont need delta work, just
998
# raw data in, and ensure that it comes out via get_lines happily.
1000
def test_add_path_to_root_no_parents_all_data(self):
1001
# The most trivial addition of a path is when there are no parents and
1002
# its in the root and all data about the file is supplied
1003
self.build_tree(['a file'])
1004
stat = os.lstat('a file')
1005
# the 1*20 is the sha1 pretend value.
1006
state = dirstate.DirState.initialize('dirstate')
1007
expected_entries = [
1008
(('', '', 'TREE_ROOT'), [
1009
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1011
(('', 'a file', 'a file id'), [
1012
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1016
state.add('a file', 'a file id', 'file', stat, '1'*20)
1017
# having added it, it should be in the output of iter_entries.
1018
self.assertEqual(expected_entries, list(state._iter_entries()))
1019
# saving and reloading should not affect this.
1023
state = dirstate.DirState.on_file('dirstate')
1026
self.assertEqual(expected_entries, list(state._iter_entries()))
1030
def test_add_path_to_unversioned_directory(self):
1031
"""Adding a path to an unversioned directory should error.
1033
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1034
once dirstate is stable and if it is merged with WorkingTree3, consider
1035
removing this copy of the test.
1037
self.build_tree(['unversioned/', 'unversioned/a file'])
1038
state = dirstate.DirState.initialize('dirstate')
1040
self.assertRaises(errors.NotVersionedError, state.add,
1041
'unversioned/a file', 'a file id', 'file', None, None)
1045
def test_add_directory_to_root_no_parents_all_data(self):
1046
# The most trivial addition of a dir is when there are no parents and
1047
# its in the root and all data about the file is supplied
1048
self.build_tree(['a dir/'])
1049
stat = os.lstat('a dir')
1050
expected_entries = [
1051
(('', '', 'TREE_ROOT'), [
1052
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1054
(('', 'a dir', 'a dir id'), [
1055
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1058
state = dirstate.DirState.initialize('dirstate')
1060
state.add('a dir', 'a dir id', 'directory', stat, None)
1061
# having added it, it should be in the output of iter_entries.
1062
self.assertEqual(expected_entries, list(state._iter_entries()))
1063
# saving and reloading should not affect this.
1067
state = dirstate.DirState.on_file('dirstate')
1071
self.assertEqual(expected_entries, list(state._iter_entries()))
1075
def test_add_symlink_to_root_no_parents_all_data(self):
1076
# The most trivial addition of a symlink when there are no parents and
1077
# its in the root and all data about the file is supplied
1078
# bzr doesn't support fake symlinks on windows, yet.
1079
self.requireFeature(SymlinkFeature)
1080
os.symlink('target', 'a link')
1081
stat = os.lstat('a link')
1082
expected_entries = [
1083
(('', '', 'TREE_ROOT'), [
1084
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1086
(('', 'a link', 'a link id'), [
1087
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1090
state = dirstate.DirState.initialize('dirstate')
1092
state.add('a link', 'a link id', 'symlink', stat, 'target')
1093
# having added it, it should be in the output of iter_entries.
1094
self.assertEqual(expected_entries, list(state._iter_entries()))
1095
# saving and reloading should not affect this.
1099
state = dirstate.DirState.on_file('dirstate')
1102
self.assertEqual(expected_entries, list(state._iter_entries()))
1106
def test_add_directory_and_child_no_parents_all_data(self):
1107
# after adding a directory, we should be able to add children to it.
1108
self.build_tree(['a dir/', 'a dir/a file'])
1109
dirstat = os.lstat('a dir')
1110
filestat = os.lstat('a dir/a file')
1111
expected_entries = [
1112
(('', '', 'TREE_ROOT'), [
1113
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1115
(('', 'a dir', 'a dir id'), [
1116
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1118
(('a dir', 'a file', 'a file id'), [
1119
('f', '1'*20, 25, False,
1120
dirstate.pack_stat(filestat)), # current tree details
1123
state = dirstate.DirState.initialize('dirstate')
1125
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1126
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
1127
# added it, it should be in the output of iter_entries.
1128
self.assertEqual(expected_entries, list(state._iter_entries()))
1129
# saving and reloading should not affect this.
1133
state = dirstate.DirState.on_file('dirstate')
1136
self.assertEqual(expected_entries, list(state._iter_entries()))
1140
def test_add_tree_reference(self):
1141
# make a dirstate and add a tree reference
1142
state = dirstate.DirState.initialize('dirstate')
1144
('', 'subdir', 'subdir-id'),
1145
[('t', 'subtree-123123', 0, False,
1146
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1149
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1150
entry = state._get_entry(0, 'subdir-id', 'subdir')
1151
self.assertEqual(entry, expected_entry)
1156
# now check we can read it back
1160
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1161
self.assertEqual(entry, entry2)
1162
self.assertEqual(entry, expected_entry)
1163
# and lookup by id should work too
1164
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1165
self.assertEqual(entry, expected_entry)
1169
def test_add_forbidden_names(self):
1170
state = dirstate.DirState.initialize('dirstate')
1171
self.addCleanup(state.unlock)
1172
self.assertRaises(errors.BzrError,
1173
state.add, '.', 'ass-id', 'directory', None, None)
1174
self.assertRaises(errors.BzrError,
1175
state.add, '..', 'ass-id', 'directory', None, None)
1178
class TestGetLines(TestCaseWithDirState):
1180
def test_get_line_with_2_rows(self):
1181
state = self.create_dirstate_with_root_and_subdir()
1183
self.assertEqual(['#bazaar dirstate flat format 3\n',
1184
'crc32: 41262208\n',
1188
'\x00\x00a-root-value\x00'
1189
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1190
'\x00subdir\x00subdir-id\x00'
1191
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1192
], state.get_lines())
1196
def test_entry_to_line(self):
1197
state = self.create_dirstate_with_root()
1200
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1201
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1202
state._entry_to_line(state._dirblocks[0][1][0]))
1206
def test_entry_to_line_with_parent(self):
1207
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1208
root_entry = ('', '', 'a-root-value'), [
1209
('d', '', 0, False, packed_stat), # current tree details
1210
# first: a pointer to the current location
1211
('a', 'dirname/basename', 0, False, ''),
1213
state = dirstate.DirState.initialize('dirstate')
1216
'\x00\x00a-root-value\x00'
1217
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1218
'a\x00dirname/basename\x000\x00n\x00',
1219
state._entry_to_line(root_entry))
1223
def test_entry_to_line_with_two_parents_at_different_paths(self):
1224
# / in the tree, at / in one parent and /dirname/basename in the other.
1225
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1226
root_entry = ('', '', 'a-root-value'), [
1227
('d', '', 0, False, packed_stat), # current tree details
1228
('d', '', 0, False, 'rev_id'), # first parent details
1229
# second: a pointer to the current location
1230
('a', 'dirname/basename', 0, False, ''),
1232
state = dirstate.DirState.initialize('dirstate')
1235
'\x00\x00a-root-value\x00'
1236
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1237
'd\x00\x000\x00n\x00rev_id\x00'
1238
'a\x00dirname/basename\x000\x00n\x00',
1239
state._entry_to_line(root_entry))
1243
def test_iter_entries(self):
1244
# we should be able to iterate the dirstate entries from end to end
1245
# this is for get_lines to be easy to read.
1246
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1248
root_entries = [(('', '', 'a-root-value'), [
1249
('d', '', 0, False, packed_stat), # current tree details
1251
dirblocks.append(('', root_entries))
1252
# add two files in the root
1253
subdir_entry = ('', 'subdir', 'subdir-id'), [
1254
('d', '', 0, False, packed_stat), # current tree details
1256
afile_entry = ('', 'afile', 'afile-id'), [
1257
('f', 'sha1value', 34, False, packed_stat), # current tree details
1259
dirblocks.append(('', [subdir_entry, afile_entry]))
1261
file_entry2 = ('subdir', '2file', '2file-id'), [
1262
('f', 'sha1value', 23, False, packed_stat), # current tree details
1264
dirblocks.append(('subdir', [file_entry2]))
1265
state = dirstate.DirState.initialize('dirstate')
1267
state._set_data([], dirblocks)
1268
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1270
self.assertEqual(expected_entries, list(state._iter_entries()))
1275
class TestGetBlockRowIndex(TestCaseWithDirState):
1277
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1278
file_present, state, dirname, basename, tree_index):
1279
self.assertEqual((block_index, row_index, dir_present, file_present),
1280
state._get_block_entry_index(dirname, basename, tree_index))
1282
block = state._dirblocks[block_index]
1283
self.assertEqual(dirname, block[0])
1284
if dir_present and file_present:
1285
row = state._dirblocks[block_index][1][row_index]
1286
self.assertEqual(dirname, row[0][0])
1287
self.assertEqual(basename, row[0][1])
1289
def test_simple_structure(self):
1290
state = self.create_dirstate_with_root_and_subdir()
1291
self.addCleanup(state.unlock)
1292
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1293
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1294
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1295
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1296
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1299
def test_complex_structure_exists(self):
1300
state = self.create_complex_dirstate()
1301
self.addCleanup(state.unlock)
1302
# Make sure we can find everything that exists
1303
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1304
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1305
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1306
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1307
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1308
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1309
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1310
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1311
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1312
'b', 'h\xc3\xa5', 0)
1314
def test_complex_structure_missing(self):
1315
state = self.create_complex_dirstate()
1316
self.addCleanup(state.unlock)
1317
# Make sure things would be inserted in the right locations
1318
# '_' comes before 'a'
1319
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1320
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1321
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1322
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1324
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1325
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1326
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1327
# This would be inserted between a/ and b/
1328
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1330
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1333
class TestGetEntry(TestCaseWithDirState):
1335
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1336
"""Check that the right entry is returned for a request to getEntry."""
1337
entry = state._get_entry(index, path_utf8=path)
1339
self.assertEqual((None, None), entry)
1342
self.assertEqual((dirname, basename, file_id), cur[:3])
1344
def test_simple_structure(self):
1345
state = self.create_dirstate_with_root_and_subdir()
1346
self.addCleanup(state.unlock)
1347
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1348
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1349
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1350
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1351
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1353
def test_complex_structure_exists(self):
1354
state = self.create_complex_dirstate()
1355
self.addCleanup(state.unlock)
1356
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1357
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1358
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1359
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1360
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1361
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1362
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1363
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1364
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1367
def test_complex_structure_missing(self):
1368
state = self.create_complex_dirstate()
1369
self.addCleanup(state.unlock)
1370
self.assertEntryEqual(None, None, None, state, '_', 0)
1371
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1372
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1373
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1375
def test_get_entry_uninitialized(self):
1376
"""Calling get_entry will load data if it needs to"""
1377
state = self.create_dirstate_with_root()
1383
state = dirstate.DirState.on_file('dirstate')
1386
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1387
state._header_state)
1388
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1389
state._dirblock_state)
1390
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1395
class TestIterChildEntries(TestCaseWithDirState):
1397
def create_dirstate_with_two_trees(self):
1398
"""This dirstate contains multiple files and directories.
1408
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1410
Notice that a/e is an empty directory.
1412
There is one parent tree, which has the same shape with the following variations:
1413
b/g in the parent is gone.
1414
b/h in the parent has a different id
1415
b/i is new in the parent
1416
c is renamed to b/j in the parent
1418
:return: The dirstate, still write-locked.
1420
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1421
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1422
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1423
root_entry = ('', '', 'a-root-value'), [
1424
('d', '', 0, False, packed_stat),
1425
('d', '', 0, False, 'parent-revid'),
1427
a_entry = ('', 'a', 'a-dir'), [
1428
('d', '', 0, False, packed_stat),
1429
('d', '', 0, False, 'parent-revid'),
1431
b_entry = ('', 'b', 'b-dir'), [
1432
('d', '', 0, False, packed_stat),
1433
('d', '', 0, False, 'parent-revid'),
1435
c_entry = ('', 'c', 'c-file'), [
1436
('f', null_sha, 10, False, packed_stat),
1437
('r', 'b/j', 0, False, ''),
1439
d_entry = ('', 'd', 'd-file'), [
1440
('f', null_sha, 20, False, packed_stat),
1441
('f', 'd', 20, False, 'parent-revid'),
1443
e_entry = ('a', 'e', 'e-dir'), [
1444
('d', '', 0, False, packed_stat),
1445
('d', '', 0, False, 'parent-revid'),
1447
f_entry = ('a', 'f', 'f-file'), [
1448
('f', null_sha, 30, False, packed_stat),
1449
('f', 'f', 20, False, 'parent-revid'),
1451
g_entry = ('b', 'g', 'g-file'), [
1452
('f', null_sha, 30, False, packed_stat),
1453
NULL_PARENT_DETAILS,
1455
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1456
('f', null_sha, 40, False, packed_stat),
1457
NULL_PARENT_DETAILS,
1459
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1460
NULL_PARENT_DETAILS,
1461
('f', 'h', 20, False, 'parent-revid'),
1463
i_entry = ('b', 'i', 'i-file'), [
1464
NULL_PARENT_DETAILS,
1465
('f', 'h', 20, False, 'parent-revid'),
1467
j_entry = ('b', 'j', 'c-file'), [
1468
('r', 'c', 0, False, ''),
1469
('f', 'j', 20, False, 'parent-revid'),
1472
dirblocks.append(('', [root_entry]))
1473
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1474
dirblocks.append(('a', [e_entry, f_entry]))
1475
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1476
state = dirstate.DirState.initialize('dirstate')
1479
state._set_data(['parent'], dirblocks)
1483
return state, dirblocks
1485
def test_iter_children_b(self):
1486
state, dirblocks = self.create_dirstate_with_two_trees()
1487
self.addCleanup(state.unlock)
1488
expected_result = []
1489
expected_result.append(dirblocks[3][1][2]) # h2
1490
expected_result.append(dirblocks[3][1][3]) # i
1491
expected_result.append(dirblocks[3][1][4]) # j
1492
self.assertEqual(expected_result,
1493
list(state._iter_child_entries(1, 'b')))
1495
def test_iter_child_root(self):
1496
state, dirblocks = self.create_dirstate_with_two_trees()
1497
self.addCleanup(state.unlock)
1498
expected_result = []
1499
expected_result.append(dirblocks[1][1][0]) # a
1500
expected_result.append(dirblocks[1][1][1]) # b
1501
expected_result.append(dirblocks[1][1][3]) # d
1502
expected_result.append(dirblocks[2][1][0]) # e
1503
expected_result.append(dirblocks[2][1][1]) # f
1504
expected_result.append(dirblocks[3][1][2]) # h2
1505
expected_result.append(dirblocks[3][1][3]) # i
1506
expected_result.append(dirblocks[3][1][4]) # j
1507
self.assertEqual(expected_result,
1508
list(state._iter_child_entries(1, '')))
1511
class TestDirstateSortOrder(TestCaseWithTransport):
1512
"""Test that DirState adds entries in the right order."""
1514
def test_add_sorting(self):
1515
"""Add entries in lexicographical order, we get path sorted order.
1517
This tests it to a depth of 4, to make sure we don't just get it right
1518
at a single depth. 'a/a' should come before 'a-a', even though it
1519
doesn't lexicographically.
1521
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1522
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1525
state = dirstate.DirState.initialize('dirstate')
1526
self.addCleanup(state.unlock)
1528
fake_stat = os.stat('dirstate')
1530
d_id = d.replace('/', '_')+'-id'
1531
file_path = d + '/f'
1532
file_id = file_path.replace('/', '_')+'-id'
1533
state.add(d, d_id, 'directory', fake_stat, null_sha)
1534
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1536
expected = ['', '', 'a',
1537
'a/a', 'a/a/a', 'a/a/a/a',
1538
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1540
split = lambda p:p.split('/')
1541
self.assertEqual(sorted(expected, key=split), expected)
1542
dirblock_names = [d[0] for d in state._dirblocks]
1543
self.assertEqual(expected, dirblock_names)
1545
def test_set_parent_trees_correct_order(self):
1546
"""After calling set_parent_trees() we should maintain the order."""
1547
dirs = ['a', 'a-a', 'a/a']
1549
state = dirstate.DirState.initialize('dirstate')
1550
self.addCleanup(state.unlock)
1552
fake_stat = os.stat('dirstate')
1554
d_id = d.replace('/', '_')+'-id'
1555
file_path = d + '/f'
1556
file_id = file_path.replace('/', '_')+'-id'
1557
state.add(d, d_id, 'directory', fake_stat, null_sha)
1558
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1560
expected = ['', '', 'a', 'a/a', 'a-a']
1561
dirblock_names = [d[0] for d in state._dirblocks]
1562
self.assertEqual(expected, dirblock_names)
1564
# *really* cheesy way to just get an empty tree
1565
repo = self.make_repository('repo')
1566
empty_tree = repo.revision_tree(None)
1567
state.set_parent_trees([('null:', empty_tree)], [])
1569
dirblock_names = [d[0] for d in state._dirblocks]
1570
self.assertEqual(expected, dirblock_names)
1573
class InstrumentedDirState(dirstate.DirState):
1574
"""An DirState with instrumented sha1 functionality."""
1576
def __init__(self, path):
1577
super(InstrumentedDirState, self).__init__(path)
1578
self._time_offset = 0
1580
# member is dynamically set in DirState.__init__ to turn on trace
1581
self._sha1_file = self._sha1_file_and_log
1583
def _sha_cutoff_time(self):
1584
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1585
self._cutoff_time = timestamp + self._time_offset
1587
def _sha1_file_and_log(self, abspath):
1588
self._log.append(('sha1', abspath))
1589
return osutils.sha_file_by_name(abspath)
1591
def _read_link(self, abspath, old_link):
1592
self._log.append(('read_link', abspath, old_link))
1593
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1595
def _lstat(self, abspath, entry):
1596
self._log.append(('lstat', abspath))
1597
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1599
def _is_executable(self, mode, old_executable):
1600
self._log.append(('is_exec', mode, old_executable))
1601
return super(InstrumentedDirState, self)._is_executable(mode,
1604
def adjust_time(self, secs):
1605
"""Move the clock forward or back.
1607
:param secs: The amount to adjust the clock by. Positive values make it
1608
seem as if we are in the future, negative values make it seem like we
1611
self._time_offset += secs
1612
self._cutoff_time = None
1615
class _FakeStat(object):
1616
"""A class with the same attributes as a real stat result."""
1618
def __init__(self, size, mtime, ctime, dev, ino, mode):
1620
self.st_mtime = mtime
1621
self.st_ctime = ctime
1627
class TestUpdateEntry(TestCaseWithDirState):
1628
"""Test the DirState.update_entry functions"""
1630
def get_state_with_a(self):
1631
"""Create a DirState tracking a single object named 'a'"""
1632
state = InstrumentedDirState.initialize('dirstate')
1633
self.addCleanup(state.unlock)
1634
state.add('a', 'a-id', 'file', None, '')
1635
entry = state._get_entry(0, path_utf8='a')
1638
def test_update_entry(self):
1639
state, entry = self.get_state_with_a()
1640
self.build_tree(['a'])
1641
# Add one where we don't provide the stat or sha already
1642
self.assertEqual(('', 'a', 'a-id'), entry[0])
1643
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1645
# Flush the buffers to disk
1647
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1648
state._dirblock_state)
1650
stat_value = os.lstat('a')
1651
packed_stat = dirstate.pack_stat(stat_value)
1652
link_or_sha1 = state.update_entry(entry, abspath='a',
1653
stat_value=stat_value)
1654
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1657
# The dirblock entry should not cache the file's sha1
1658
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1660
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1661
state._dirblock_state)
1662
mode = stat_value.st_mode
1663
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1666
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1667
state._dirblock_state)
1669
# If we do it again right away, we don't know if the file has changed
1670
# so we will re-read the file. Roll the clock back so the file is
1671
# guaranteed to look too new.
1672
state.adjust_time(-10)
1674
link_or_sha1 = state.update_entry(entry, abspath='a',
1675
stat_value=stat_value)
1676
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1677
('sha1', 'a'), ('is_exec', mode, False),
1679
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1681
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1682
state._dirblock_state)
1683
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1687
# However, if we move the clock forward so the file is considered
1688
# "stable", it should just cache the value.
1689
state.adjust_time(+20)
1690
link_or_sha1 = state.update_entry(entry, abspath='a',
1691
stat_value=stat_value)
1692
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1694
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1695
('sha1', 'a'), ('is_exec', mode, False),
1696
('sha1', 'a'), ('is_exec', mode, False),
1698
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1701
# Subsequent calls will just return the cached value
1702
link_or_sha1 = state.update_entry(entry, abspath='a',
1703
stat_value=stat_value)
1704
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1706
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1707
('sha1', 'a'), ('is_exec', mode, False),
1708
('sha1', 'a'), ('is_exec', mode, False),
1710
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1713
def test_update_entry_symlink(self):
1714
"""Update entry should read symlinks."""
1715
self.requireFeature(SymlinkFeature)
1716
state, entry = self.get_state_with_a()
1718
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1719
state._dirblock_state)
1720
os.symlink('target', 'a')
1722
state.adjust_time(-10) # Make the symlink look new
1723
stat_value = os.lstat('a')
1724
packed_stat = dirstate.pack_stat(stat_value)
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', '')], state._log)
1729
# Dirblock is not updated (the link is too new)
1730
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1732
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1733
state._dirblock_state)
1735
# Because the stat_value looks new, we should re-read the target
1736
link_or_sha1 = state.update_entry(entry, abspath='a',
1737
stat_value=stat_value)
1738
self.assertEqual('target', link_or_sha1)
1739
self.assertEqual([('read_link', 'a', ''),
1740
('read_link', 'a', ''),
1742
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1744
state.adjust_time(+20) # Skip into the future, all files look old
1745
link_or_sha1 = state.update_entry(entry, abspath='a',
1746
stat_value=stat_value)
1747
self.assertEqual('target', link_or_sha1)
1748
# We need to re-read the link because only now can we cache it
1749
self.assertEqual([('read_link', 'a', ''),
1750
('read_link', 'a', ''),
1751
('read_link', 'a', ''),
1753
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1756
# Another call won't re-read the link
1757
self.assertEqual([('read_link', 'a', ''),
1758
('read_link', 'a', ''),
1759
('read_link', 'a', ''),
1761
link_or_sha1 = state.update_entry(entry, abspath='a',
1762
stat_value=stat_value)
1763
self.assertEqual('target', link_or_sha1)
1764
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1767
def do_update_entry(self, state, entry, abspath):
1768
stat_value = os.lstat(abspath)
1769
return state.update_entry(entry, abspath, stat_value)
1771
def test_update_entry_dir(self):
1772
state, entry = self.get_state_with_a()
1773
self.build_tree(['a/'])
1774
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1776
def test_update_entry_dir_unchanged(self):
1777
state, entry = self.get_state_with_a()
1778
self.build_tree(['a/'])
1779
state.adjust_time(+20)
1780
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1781
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1782
state._dirblock_state)
1784
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1785
state._dirblock_state)
1786
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1787
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1788
state._dirblock_state)
1790
def test_update_entry_file_unchanged(self):
1791
state, entry = self.get_state_with_a()
1792
self.build_tree(['a'])
1793
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1794
state.adjust_time(+20)
1795
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1796
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1797
state._dirblock_state)
1799
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1800
state._dirblock_state)
1801
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1802
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1803
state._dirblock_state)
1805
def create_and_test_file(self, state, entry):
1806
"""Create a file at 'a' and verify the state finds it.
1808
The state should already be versioning *something* at 'a'. This makes
1809
sure that state.update_entry recognizes it as a file.
1811
self.build_tree(['a'])
1812
stat_value = os.lstat('a')
1813
packed_stat = dirstate.pack_stat(stat_value)
1815
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1816
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1818
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1822
def create_and_test_dir(self, state, entry):
1823
"""Create a directory at 'a' and verify the state finds it.
1825
The state should already be versioning *something* at 'a'. This makes
1826
sure that state.update_entry recognizes it as a directory.
1828
self.build_tree(['a/'])
1829
stat_value = os.lstat('a')
1830
packed_stat = dirstate.pack_stat(stat_value)
1832
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1833
self.assertIs(None, link_or_sha1)
1834
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1838
def create_and_test_symlink(self, state, entry):
1839
"""Create a symlink at 'a' and verify the state finds it.
1841
The state should already be versioning *something* at 'a'. This makes
1842
sure that state.update_entry recognizes it as a symlink.
1844
This should not be called if this platform does not have symlink
1847
# caller should care about skipping test on platforms without symlinks
1848
os.symlink('path/to/foo', 'a')
1850
stat_value = os.lstat('a')
1851
packed_stat = dirstate.pack_stat(stat_value)
1853
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1854
self.assertEqual('path/to/foo', link_or_sha1)
1855
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1859
def test_update_file_to_dir(self):
1860
"""If a file changes to a directory we return None for the sha.
1861
We also update the inventory record.
1863
state, entry = self.get_state_with_a()
1864
# The file sha1 won't be cached unless the file is old
1865
state.adjust_time(+10)
1866
self.create_and_test_file(state, entry)
1868
self.create_and_test_dir(state, entry)
1870
def test_update_file_to_symlink(self):
1871
"""File becomes a symlink"""
1872
self.requireFeature(SymlinkFeature)
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_file(state, entry)
1878
self.create_and_test_symlink(state, entry)
1880
def test_update_dir_to_file(self):
1881
"""Directory becoming a file updates the entry."""
1882
state, entry = self.get_state_with_a()
1883
# The file sha1 won't be cached unless the file is old
1884
state.adjust_time(+10)
1885
self.create_and_test_dir(state, entry)
1887
self.create_and_test_file(state, entry)
1889
def test_update_dir_to_symlink(self):
1890
"""Directory becomes a symlink"""
1891
self.requireFeature(SymlinkFeature)
1892
state, entry = self.get_state_with_a()
1893
# The symlink target won't be cached if it isn't old
1894
state.adjust_time(+10)
1895
self.create_and_test_dir(state, entry)
1897
self.create_and_test_symlink(state, entry)
1899
def test_update_symlink_to_file(self):
1900
"""Symlink becomes a file"""
1901
self.requireFeature(SymlinkFeature)
1902
state, entry = self.get_state_with_a()
1903
# The symlink and file info won't be cached unless old
1904
state.adjust_time(+10)
1905
self.create_and_test_symlink(state, entry)
1907
self.create_and_test_file(state, entry)
1909
def test_update_symlink_to_dir(self):
1910
"""Symlink becomes a directory"""
1911
self.requireFeature(SymlinkFeature)
1912
state, entry = self.get_state_with_a()
1913
# The symlink target won't be cached if it isn't old
1914
state.adjust_time(+10)
1915
self.create_and_test_symlink(state, entry)
1917
self.create_and_test_dir(state, entry)
1919
def test__is_executable_win32(self):
1920
state, entry = self.get_state_with_a()
1921
self.build_tree(['a'])
1923
# Make sure we are using the win32 implementation of _is_executable
1924
state._is_executable = state._is_executable_win32
1926
# The file on disk is not executable, but we are marking it as though
1927
# it is. With _is_executable_win32 we ignore what is on disk.
1928
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1930
stat_value = os.lstat('a')
1931
packed_stat = dirstate.pack_stat(stat_value)
1933
state.adjust_time(-10) # Make sure everything is new
1934
state.update_entry(entry, abspath='a', stat_value=stat_value)
1936
# The row is updated, but the executable bit stays set.
1937
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1940
# Make the disk object look old enough to cache
1941
state.adjust_time(+20)
1942
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1943
state.update_entry(entry, abspath='a', stat_value=stat_value)
1944
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1947
class TestPackStat(TestCaseWithTransport):
1949
def assertPackStat(self, expected, stat_value):
1950
"""Check the packed and serialized form of a stat value."""
1951
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1953
def test_pack_stat_int(self):
1954
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1955
# Make sure that all parameters have an impact on the packed stat.
1956
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1959
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1960
st.st_mtime = 1172758620
1962
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1963
st.st_ctime = 1172758630
1965
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1968
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1969
st.st_ino = 6499540L
1971
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1972
st.st_mode = 0100744
1974
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1976
def test_pack_stat_float(self):
1977
"""On some platforms mtime and ctime are floats.
1979
Make sure we don't get warnings or errors, and that we ignore changes <
1982
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1983
777L, 6499538L, 0100644)
1984
# These should all be the same as the integer counterparts
1985
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1986
st.st_mtime = 1172758620.0
1988
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1989
st.st_ctime = 1172758630.0
1991
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1992
# fractional seconds are discarded, so no change from above
1993
st.st_mtime = 1172758620.453
1994
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1995
st.st_ctime = 1172758630.228
1996
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1999
class TestBisect(TestCaseWithDirState):
2000
"""Test the ability to bisect into the disk format."""
2002
def assertBisect(self, expected_map, map_keys, state, paths):
2003
"""Assert that bisecting for paths returns the right result.
2005
:param expected_map: A map from key => entry value
2006
:param map_keys: The keys to expect for each path
2007
:param state: The DirState object.
2008
:param paths: A list of paths, these will automatically be split into
2009
(dir, name) tuples, and sorted according to how _bisect
2012
result = state._bisect(paths)
2013
# For now, results are just returned in whatever order we read them.
2014
# We could sort by (dir, name, file_id) or something like that, but in
2015
# the end it would still be fairly arbitrary, and we don't want the
2016
# extra overhead if we can avoid it. So sort everything to make sure
2018
assert len(map_keys) == len(paths)
2020
for path, keys in zip(paths, map_keys):
2022
# This should not be present in the output
2024
expected[path] = sorted(expected_map[k] for k in keys)
2026
# The returned values are just arranged randomly based on when they
2027
# were read, for testing, make sure it is properly sorted.
2031
self.assertEqual(expected, result)
2033
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
2034
"""Assert that bisecting for dirbblocks returns the right result.
2036
:param expected_map: A map from key => expected values
2037
:param map_keys: A nested list of paths we expect to be returned.
2038
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
2039
:param state: The DirState object.
2040
:param paths: A list of directories
2042
result = state._bisect_dirblocks(paths)
2043
assert len(map_keys) == len(paths)
2046
for path, keys in zip(paths, map_keys):
2048
# This should not be present in the output
2050
expected[path] = sorted(expected_map[k] for k in keys)
2054
self.assertEqual(expected, result)
2056
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
2057
"""Assert the return value of a recursive bisection.
2059
:param expected_map: A map from key => entry value
2060
:param map_keys: A list of paths we expect to be returned.
2061
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
2062
:param state: The DirState object.
2063
:param paths: A list of files and directories. It will be broken up
2064
into (dir, name) pairs and sorted before calling _bisect_recursive.
2067
for key in map_keys:
2068
entry = expected_map[key]
2069
dir_name_id, trees_info = entry
2070
expected[dir_name_id] = trees_info
2072
result = state._bisect_recursive(paths)
2074
self.assertEqual(expected, result)
2076
def test_bisect_each(self):
2077
"""Find a single record using bisect."""
2078
tree, state, expected = self.create_basic_dirstate()
2080
# Bisect should return the rows for the specified files.
2081
self.assertBisect(expected, [['']], state, [''])
2082
self.assertBisect(expected, [['a']], state, ['a'])
2083
self.assertBisect(expected, [['b']], state, ['b'])
2084
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2085
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2086
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2087
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2088
self.assertBisect(expected, [['f']], state, ['f'])
2090
def test_bisect_multi(self):
2091
"""Bisect can be used to find multiple records at the same time."""
2092
tree, state, expected = self.create_basic_dirstate()
2093
# Bisect should be capable of finding multiple entries at the same time
2094
self.assertBisect(expected, [['a'], ['b'], ['f']],
2095
state, ['a', 'b', 'f'])
2096
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2097
state, ['f', 'b/d', 'b/d/e'])
2098
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2099
state, ['b', 'b-c', 'b/c'])
2101
def test_bisect_one_page(self):
2102
"""Test bisect when there is only 1 page to read"""
2103
tree, state, expected = self.create_basic_dirstate()
2104
state._bisect_page_size = 5000
2105
self.assertBisect(expected,[['']], state, [''])
2106
self.assertBisect(expected,[['a']], state, ['a'])
2107
self.assertBisect(expected,[['b']], state, ['b'])
2108
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2109
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2110
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2111
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2112
self.assertBisect(expected,[['f']], state, ['f'])
2113
self.assertBisect(expected,[['a'], ['b'], ['f']],
2114
state, ['a', 'b', 'f'])
2115
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2116
state, ['b/d', 'b/d/e', 'f'])
2117
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2118
state, ['b', 'b/c', 'b-c'])
2120
def test_bisect_duplicate_paths(self):
2121
"""When bisecting for a path, handle multiple entries."""
2122
tree, state, expected = self.create_duplicated_dirstate()
2124
# Now make sure that both records are properly returned.
2125
self.assertBisect(expected, [['']], state, [''])
2126
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2127
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2128
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2129
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2130
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2132
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2133
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2135
def test_bisect_page_size_too_small(self):
2136
"""If the page size is too small, we will auto increase it."""
2137
tree, state, expected = self.create_basic_dirstate()
2138
state._bisect_page_size = 50
2139
self.assertBisect(expected, [None], state, ['b/e'])
2140
self.assertBisect(expected, [['a']], state, ['a'])
2141
self.assertBisect(expected, [['b']], state, ['b'])
2142
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2143
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2144
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2145
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2146
self.assertBisect(expected, [['f']], state, ['f'])
2148
def test_bisect_missing(self):
2149
"""Test that bisect return None if it cannot find a path."""
2150
tree, state, expected = self.create_basic_dirstate()
2151
self.assertBisect(expected, [None], state, ['foo'])
2152
self.assertBisect(expected, [None], state, ['b/foo'])
2153
self.assertBisect(expected, [None], state, ['bar/foo'])
2154
self.assertBisect(expected, [None], state, ['b-c/foo'])
2156
self.assertBisect(expected, [['a'], None, ['b/d']],
2157
state, ['a', 'foo', 'b/d'])
2159
def test_bisect_rename(self):
2160
"""Check that we find a renamed row."""
2161
tree, state, expected = self.create_renamed_dirstate()
2163
# Search for the pre and post renamed entries
2164
self.assertBisect(expected, [['a']], state, ['a'])
2165
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2166
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2167
self.assertBisect(expected, [['h']], state, ['h'])
2169
# What about b/d/e? shouldn't that also get 2 directory entries?
2170
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2171
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2173
def test_bisect_dirblocks(self):
2174
tree, state, expected = self.create_duplicated_dirstate()
2175
self.assertBisectDirBlocks(expected,
2176
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2178
self.assertBisectDirBlocks(expected,
2179
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2180
self.assertBisectDirBlocks(expected,
2181
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2182
self.assertBisectDirBlocks(expected,
2183
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2184
['b/c', 'b/c2', 'b/d', 'b/d2'],
2185
['b/d/e', 'b/d/e2'],
2186
], state, ['', 'b', 'b/d'])
2188
def test_bisect_dirblocks_missing(self):
2189
tree, state, expected = self.create_basic_dirstate()
2190
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2191
state, ['b/d', 'b/e'])
2192
# Files don't show up in this search
2193
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2194
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2195
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2196
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2197
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2199
def test_bisect_recursive_each(self):
2200
tree, state, expected = self.create_basic_dirstate()
2201
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2202
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2203
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2204
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2205
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2207
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2209
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2213
def test_bisect_recursive_multiple(self):
2214
tree, state, expected = self.create_basic_dirstate()
2215
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2216
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2217
state, ['b/d', 'b/d/e'])
2219
def test_bisect_recursive_missing(self):
2220
tree, state, expected = self.create_basic_dirstate()
2221
self.assertBisectRecursive(expected, [], state, ['d'])
2222
self.assertBisectRecursive(expected, [], state, ['b/e'])
2223
self.assertBisectRecursive(expected, [], state, ['g'])
2224
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2226
def test_bisect_recursive_renamed(self):
2227
tree, state, expected = self.create_renamed_dirstate()
2229
# Looking for either renamed item should find the other
2230
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2231
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2232
# Looking in the containing directory should find the rename target,
2233
# and anything in a subdir of the renamed target.
2234
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2235
'b/d/e', 'b/g', 'h', 'h/e'],
2239
class TestDirstateValidation(TestCaseWithDirState):
2241
def test_validate_correct_dirstate(self):
2242
state = self.create_complex_dirstate()
2245
# and make sure we can also validate with a read lock
2252
def test_dirblock_not_sorted(self):
2253
tree, state, expected = self.create_renamed_dirstate()
2254
state._read_dirblocks_if_needed()
2255
last_dirblock = state._dirblocks[-1]
2256
# we're appending to the dirblock, but this name comes before some of
2257
# the existing names; that's wrong
2258
last_dirblock[1].append(
2259
(('h', 'aaaa', 'a-id'),
2260
[('a', '', 0, False, ''),
2261
('a', '', 0, False, '')]))
2262
e = self.assertRaises(AssertionError,
2264
self.assertContainsRe(str(e), 'not sorted')
2266
def test_dirblock_name_mismatch(self):
2267
tree, state, expected = self.create_renamed_dirstate()
2268
state._read_dirblocks_if_needed()
2269
last_dirblock = state._dirblocks[-1]
2270
# add an entry with the wrong directory name
2271
last_dirblock[1].append(
2273
[('a', '', 0, False, ''),
2274
('a', '', 0, False, '')]))
2275
e = self.assertRaises(AssertionError,
2277
self.assertContainsRe(str(e),
2278
"doesn't match directory name")
2280
def test_dirblock_missing_rename(self):
2281
tree, state, expected = self.create_renamed_dirstate()
2282
state._read_dirblocks_if_needed()
2283
last_dirblock = state._dirblocks[-1]
2284
# make another entry for a-id, without a correct 'r' pointer to
2285
# the real occurrence in the working tree
2286
last_dirblock[1].append(
2287
(('h', 'z', 'a-id'),
2288
[('a', '', 0, False, ''),
2289
('a', '', 0, False, '')]))
2290
e = self.assertRaises(AssertionError,
2292
self.assertContainsRe(str(e),
2293
'file a-id is absent in row')
2296
class TestDirstateTreeReference(TestCaseWithDirState):
2298
def test_reference_revision_is_none(self):
2299
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2300
subtree = self.make_branch_and_tree('tree/subtree',
2301
format='dirstate-with-subtree')
2302
subtree.set_root_id('subtree')
2303
tree.add_reference(subtree)
2305
state = dirstate.DirState.from_tree(tree, 'dirstate')
2306
key = ('', 'subtree', 'subtree')
2307
expected = ('', [(key,
2308
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2311
self.assertEqual(expected, state._find_block(key))