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.get_root_id()), # 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.get_root_id()), # 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.get_root_id()), # 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.get_root_id()), # 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.get_root_id()), # 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.get_root_id()), # 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.get_root_id()), # 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.get_root_id()), # 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])
649
def test_save_refuses_if_changes_aborted(self):
650
self.build_tree(['a-file', 'a-dir/'])
651
state = dirstate.DirState.initialize('dirstate')
653
# No stat and no sha1 sum.
654
state.add('a-file', 'a-file-id', 'file', None, '')
659
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
661
('', [(('', '', 'TREE_ROOT'),
662
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
663
('', [(('', 'a-file', 'a-file-id'),
664
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
667
state = dirstate.DirState.on_file('dirstate')
670
state._read_dirblocks_if_needed()
671
self.assertEqual(expected_blocks, state._dirblocks)
673
# Now modify the state, but mark it as inconsistent
674
state.add('a-dir', 'a-dir-id', 'directory', None, '')
675
state._changes_aborted = True
680
state = dirstate.DirState.on_file('dirstate')
683
state._read_dirblocks_if_needed()
684
self.assertEqual(expected_blocks, state._dirblocks)
689
class TestDirStateInitialize(TestCaseWithDirState):
691
def test_initialize(self):
692
expected_result = ([], [
693
(('', '', 'TREE_ROOT'), # common details
694
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
697
state = dirstate.DirState.initialize('dirstate')
699
self.assertIsInstance(state, dirstate.DirState)
700
lines = state.get_lines()
703
# On win32 you can't read from a locked file, even within the same
704
# process. So we have to unlock and release before we check the file
706
self.assertFileEqual(''.join(lines), 'dirstate')
707
state.lock_read() # check_state_with_reopen will unlock
708
self.check_state_with_reopen(expected_result, state)
711
class TestDirStateManipulations(TestCaseWithDirState):
713
def test_set_state_from_inventory_no_content_no_parents(self):
714
# setting the current inventory is a slow but important api to support.
715
tree1 = self.make_branch_and_memory_tree('tree1')
719
revid1 = tree1.commit('foo').encode('utf8')
720
root_id = tree1.get_root_id()
721
inv = tree1.inventory
724
expected_result = [], [
725
(('', '', root_id), [
726
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
727
state = dirstate.DirState.initialize('dirstate')
729
state.set_state_from_inventory(inv)
730
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
732
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
733
state._dirblock_state)
738
# This will unlock it
739
self.check_state_with_reopen(expected_result, state)
741
def test_set_state_from_inventory_preserves_hashcache(self):
742
# https://bugs.launchpad.net/bzr/+bug/146176
743
# set_state_from_inventory should preserve the stat and hash value for
744
# workingtree files that are not changed by the inventory.
746
tree = self.make_branch_and_tree('.')
747
# depends on the default format using dirstate...
750
# make a dirstate with some valid hashcache data
751
# file on disk, but that's not needed for this test
752
foo_contents = 'contents of foo'
753
self.build_tree_contents([('foo', foo_contents)])
754
tree.add('foo', 'foo-id')
756
foo_stat = os.stat('foo')
757
foo_packed = dirstate.pack_stat(foo_stat)
758
foo_sha = osutils.sha_string(foo_contents)
759
foo_size = len(foo_contents)
761
# should not be cached yet, because the file's too fresh
763
(('', 'foo', 'foo-id',),
764
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
765
tree._dirstate._get_entry(0, 'foo-id'))
766
# poke in some hashcache information - it wouldn't normally be
767
# stored because it's too fresh
768
tree._dirstate.update_minimal(
769
('', 'foo', 'foo-id'),
770
'f', False, foo_sha, foo_packed, foo_size, 'foo')
771
# now should be cached
773
(('', 'foo', 'foo-id',),
774
[('f', foo_sha, foo_size, False, foo_packed)]),
775
tree._dirstate._get_entry(0, 'foo-id'))
777
# extract the inventory, and add something to it
778
inv = tree._get_inventory()
779
# should see the file we poked in...
780
self.assertTrue(inv.has_id('foo-id'))
781
self.assertTrue(inv.has_filename('foo'))
782
inv.add_path('bar', 'file', 'bar-id')
783
tree._dirstate._validate()
784
# this used to cause it to lose its hashcache
785
tree._dirstate.set_state_from_inventory(inv)
786
tree._dirstate._validate()
792
# now check that the state still has the original hashcache value
793
state = tree._dirstate
795
foo_tuple = state._get_entry(0, path_utf8='foo')
797
(('', 'foo', 'foo-id',),
798
[('f', foo_sha, len(foo_contents), False,
799
dirstate.pack_stat(foo_stat))]),
805
def test_set_state_from_inventory_mixed_paths(self):
806
tree1 = self.make_branch_and_tree('tree1')
807
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
808
'tree1/a/b/foo', 'tree1/a-b/bar'])
811
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
812
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
813
tree1.commit('rev1', rev_id='rev1')
814
root_id = tree1.get_root_id()
815
inv = tree1.inventory
818
expected_result1 = [('', '', root_id, 'd'),
819
('', 'a', 'a-id', 'd'),
820
('', 'a-b', 'a-b-id', 'd'),
821
('a', 'b', 'b-id', 'd'),
822
('a/b', 'foo', 'foo-id', 'f'),
823
('a-b', 'bar', 'bar-id', 'f'),
825
expected_result2 = [('', '', root_id, 'd'),
826
('', 'a', 'a-id', 'd'),
827
('', 'a-b', 'a-b-id', 'd'),
828
('a-b', 'bar', 'bar-id', 'f'),
830
state = dirstate.DirState.initialize('dirstate')
832
state.set_state_from_inventory(inv)
834
for entry in state._iter_entries():
835
values.append(entry[0] + entry[1][0][:1])
836
self.assertEqual(expected_result1, values)
838
state.set_state_from_inventory(inv)
840
for entry in state._iter_entries():
841
values.append(entry[0] + entry[1][0][:1])
842
self.assertEqual(expected_result2, values)
846
def test_set_path_id_no_parents(self):
847
"""The id of a path can be changed trivally with no parents."""
848
state = dirstate.DirState.initialize('dirstate')
850
# check precondition to be sure the state does change appropriately.
852
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
853
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
854
list(state._iter_entries()))
855
state.set_path_id('', 'foobarbaz')
857
(('', '', 'foobarbaz'), [('d', '', 0, False,
858
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
859
self.assertEqual(expected_rows, list(state._iter_entries()))
860
# should work across save too
864
state = dirstate.DirState.on_file('dirstate')
868
self.assertEqual(expected_rows, list(state._iter_entries()))
872
def test_set_path_id_with_parents(self):
873
"""Set the root file id in a dirstate with parents"""
874
mt = self.make_branch_and_tree('mt')
875
# in case the default tree format uses a different root id
876
mt.set_root_id('TREE_ROOT')
877
mt.commit('foo', rev_id='parent-revid')
878
rt = mt.branch.repository.revision_tree('parent-revid')
879
state = dirstate.DirState.initialize('dirstate')
882
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
883
state.set_path_id('', 'foobarbaz')
885
# now see that it is what we expected
887
(('', '', 'TREE_ROOT'),
888
[('a', '', 0, False, ''),
889
('d', '', 0, False, 'parent-revid'),
891
(('', '', 'foobarbaz'),
892
[('d', '', 0, False, ''),
893
('a', '', 0, False, ''),
897
self.assertEqual(expected_rows, list(state._iter_entries()))
898
# should work across save too
902
# now flush & check we get the same
903
state = dirstate.DirState.on_file('dirstate')
907
self.assertEqual(expected_rows, list(state._iter_entries()))
910
# now change within an existing file-backed state
914
state.set_path_id('', 'tree-root-2')
920
def test_set_parent_trees_no_content(self):
921
# set_parent_trees is a slow but important api to support.
922
tree1 = self.make_branch_and_memory_tree('tree1')
926
revid1 = tree1.commit('foo')
929
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
930
tree2 = MemoryTree.create_on_branch(branch2)
933
revid2 = tree2.commit('foo')
934
root_id = tree2.get_root_id()
937
state = dirstate.DirState.initialize('dirstate')
939
state.set_path_id('', root_id)
940
state.set_parent_trees(
941
((revid1, tree1.branch.repository.revision_tree(revid1)),
942
(revid2, tree2.branch.repository.revision_tree(revid2)),
943
('ghost-rev', None)),
945
# check we can reopen and use the dirstate after setting parent
952
state = dirstate.DirState.on_file('dirstate')
955
self.assertEqual([revid1, revid2, 'ghost-rev'],
956
state.get_parent_ids())
957
# iterating the entire state ensures that the state is parsable.
958
list(state._iter_entries())
959
# be sure that it sets not appends - change it
960
state.set_parent_trees(
961
((revid1, tree1.branch.repository.revision_tree(revid1)),
962
('ghost-rev', None)),
964
# and now put it back.
965
state.set_parent_trees(
966
((revid1, tree1.branch.repository.revision_tree(revid1)),
967
(revid2, tree2.branch.repository.revision_tree(revid2)),
968
('ghost-rev', tree2.branch.repository.revision_tree(None))),
970
self.assertEqual([revid1, revid2, 'ghost-rev'],
971
state.get_parent_ids())
972
# the ghost should be recorded as such by set_parent_trees.
973
self.assertEqual(['ghost-rev'], state.get_ghosts())
975
[(('', '', root_id), [
976
('d', '', 0, False, dirstate.DirState.NULLSTAT),
977
('d', '', 0, False, revid1),
978
('d', '', 0, False, revid2)
980
list(state._iter_entries()))
984
def test_set_parent_trees_file_missing_from_tree(self):
985
# Adding a parent tree may reference files not in the current state.
986
# they should get listed just once by id, even if they are in two
988
# set_parent_trees is a slow but important api to support.
989
tree1 = self.make_branch_and_memory_tree('tree1')
993
tree1.add(['a file'], ['file-id'], ['file'])
994
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
995
revid1 = tree1.commit('foo')
998
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
999
tree2 = MemoryTree.create_on_branch(branch2)
1002
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1003
revid2 = tree2.commit('foo')
1004
root_id = tree2.get_root_id()
1007
# check the layout in memory
1008
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1009
(('', '', root_id), [
1010
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1011
('d', '', 0, False, revid1.encode('utf8')),
1012
('d', '', 0, False, revid2.encode('utf8'))
1014
(('', 'a file', 'file-id'), [
1015
('a', '', 0, False, ''),
1016
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1017
revid1.encode('utf8')),
1018
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1019
revid2.encode('utf8'))
1022
state = dirstate.DirState.initialize('dirstate')
1024
state.set_path_id('', root_id)
1025
state.set_parent_trees(
1026
((revid1, tree1.branch.repository.revision_tree(revid1)),
1027
(revid2, tree2.branch.repository.revision_tree(revid2)),
1033
# check_state_with_reopen will unlock
1034
self.check_state_with_reopen(expected_result, state)
1036
### add a path via _set_data - so we dont need delta work, just
1037
# raw data in, and ensure that it comes out via get_lines happily.
1039
def test_add_path_to_root_no_parents_all_data(self):
1040
# The most trivial addition of a path is when there are no parents and
1041
# its in the root and all data about the file is supplied
1042
self.build_tree(['a file'])
1043
stat = os.lstat('a file')
1044
# the 1*20 is the sha1 pretend value.
1045
state = dirstate.DirState.initialize('dirstate')
1046
expected_entries = [
1047
(('', '', 'TREE_ROOT'), [
1048
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1050
(('', 'a file', 'a-file-id'), [
1051
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1055
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1056
# having added it, it should be in the output of iter_entries.
1057
self.assertEqual(expected_entries, list(state._iter_entries()))
1058
# saving and reloading should not affect this.
1062
state = dirstate.DirState.on_file('dirstate')
1065
self.assertEqual(expected_entries, list(state._iter_entries()))
1069
def test_add_path_to_unversioned_directory(self):
1070
"""Adding a path to an unversioned directory should error.
1072
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1073
once dirstate is stable and if it is merged with WorkingTree3, consider
1074
removing this copy of the test.
1076
self.build_tree(['unversioned/', 'unversioned/a file'])
1077
state = dirstate.DirState.initialize('dirstate')
1079
self.assertRaises(errors.NotVersionedError, state.add,
1080
'unversioned/a file', 'a-file-id', 'file', None, None)
1084
def test_add_directory_to_root_no_parents_all_data(self):
1085
# The most trivial addition of a dir is when there are no parents and
1086
# its in the root and all data about the file is supplied
1087
self.build_tree(['a dir/'])
1088
stat = os.lstat('a dir')
1089
expected_entries = [
1090
(('', '', 'TREE_ROOT'), [
1091
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1093
(('', 'a dir', 'a dir id'), [
1094
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1097
state = dirstate.DirState.initialize('dirstate')
1099
state.add('a dir', 'a dir id', 'directory', stat, None)
1100
# having added it, it should be in the output of iter_entries.
1101
self.assertEqual(expected_entries, list(state._iter_entries()))
1102
# saving and reloading should not affect this.
1106
state = dirstate.DirState.on_file('dirstate')
1110
self.assertEqual(expected_entries, list(state._iter_entries()))
1114
def test_add_symlink_to_root_no_parents_all_data(self):
1115
# The most trivial addition of a symlink when there are no parents and
1116
# its in the root and all data about the file is supplied
1117
# bzr doesn't support fake symlinks on windows, yet.
1118
self.requireFeature(SymlinkFeature)
1119
os.symlink('target', 'a link')
1120
stat = os.lstat('a link')
1121
expected_entries = [
1122
(('', '', 'TREE_ROOT'), [
1123
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1125
(('', 'a link', 'a link id'), [
1126
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1129
state = dirstate.DirState.initialize('dirstate')
1131
state.add('a link', 'a link id', 'symlink', stat, 'target')
1132
# having added it, it should be in the output of iter_entries.
1133
self.assertEqual(expected_entries, list(state._iter_entries()))
1134
# saving and reloading should not affect this.
1138
state = dirstate.DirState.on_file('dirstate')
1141
self.assertEqual(expected_entries, list(state._iter_entries()))
1145
def test_add_directory_and_child_no_parents_all_data(self):
1146
# after adding a directory, we should be able to add children to it.
1147
self.build_tree(['a dir/', 'a dir/a file'])
1148
dirstat = os.lstat('a dir')
1149
filestat = os.lstat('a dir/a file')
1150
expected_entries = [
1151
(('', '', 'TREE_ROOT'), [
1152
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1154
(('', 'a dir', 'a dir id'), [
1155
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1157
(('a dir', 'a file', 'a-file-id'), [
1158
('f', '1'*20, 25, False,
1159
dirstate.pack_stat(filestat)), # current tree details
1162
state = dirstate.DirState.initialize('dirstate')
1164
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1165
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1166
# added it, it should be in the output of iter_entries.
1167
self.assertEqual(expected_entries, list(state._iter_entries()))
1168
# saving and reloading should not affect this.
1172
state = dirstate.DirState.on_file('dirstate')
1175
self.assertEqual(expected_entries, list(state._iter_entries()))
1179
def test_add_tree_reference(self):
1180
# make a dirstate and add a tree reference
1181
state = dirstate.DirState.initialize('dirstate')
1183
('', 'subdir', 'subdir-id'),
1184
[('t', 'subtree-123123', 0, False,
1185
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1188
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1189
entry = state._get_entry(0, 'subdir-id', 'subdir')
1190
self.assertEqual(entry, expected_entry)
1195
# now check we can read it back
1199
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1200
self.assertEqual(entry, entry2)
1201
self.assertEqual(entry, expected_entry)
1202
# and lookup by id should work too
1203
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1204
self.assertEqual(entry, expected_entry)
1208
def test_add_forbidden_names(self):
1209
state = dirstate.DirState.initialize('dirstate')
1210
self.addCleanup(state.unlock)
1211
self.assertRaises(errors.BzrError,
1212
state.add, '.', 'ass-id', 'directory', None, None)
1213
self.assertRaises(errors.BzrError,
1214
state.add, '..', 'ass-id', 'directory', None, None)
1217
class TestGetLines(TestCaseWithDirState):
1219
def test_get_line_with_2_rows(self):
1220
state = self.create_dirstate_with_root_and_subdir()
1222
self.assertEqual(['#bazaar dirstate flat format 3\n',
1223
'crc32: 41262208\n',
1227
'\x00\x00a-root-value\x00'
1228
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1229
'\x00subdir\x00subdir-id\x00'
1230
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1231
], state.get_lines())
1235
def test_entry_to_line(self):
1236
state = self.create_dirstate_with_root()
1239
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1240
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1241
state._entry_to_line(state._dirblocks[0][1][0]))
1245
def test_entry_to_line_with_parent(self):
1246
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1247
root_entry = ('', '', 'a-root-value'), [
1248
('d', '', 0, False, packed_stat), # current tree details
1249
# first: a pointer to the current location
1250
('a', 'dirname/basename', 0, False, ''),
1252
state = dirstate.DirState.initialize('dirstate')
1255
'\x00\x00a-root-value\x00'
1256
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1257
'a\x00dirname/basename\x000\x00n\x00',
1258
state._entry_to_line(root_entry))
1262
def test_entry_to_line_with_two_parents_at_different_paths(self):
1263
# / in the tree, at / in one parent and /dirname/basename in the other.
1264
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1265
root_entry = ('', '', 'a-root-value'), [
1266
('d', '', 0, False, packed_stat), # current tree details
1267
('d', '', 0, False, 'rev_id'), # first parent details
1268
# second: a pointer to the current location
1269
('a', 'dirname/basename', 0, False, ''),
1271
state = dirstate.DirState.initialize('dirstate')
1274
'\x00\x00a-root-value\x00'
1275
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1276
'd\x00\x000\x00n\x00rev_id\x00'
1277
'a\x00dirname/basename\x000\x00n\x00',
1278
state._entry_to_line(root_entry))
1282
def test_iter_entries(self):
1283
# we should be able to iterate the dirstate entries from end to end
1284
# this is for get_lines to be easy to read.
1285
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1287
root_entries = [(('', '', 'a-root-value'), [
1288
('d', '', 0, False, packed_stat), # current tree details
1290
dirblocks.append(('', root_entries))
1291
# add two files in the root
1292
subdir_entry = ('', 'subdir', 'subdir-id'), [
1293
('d', '', 0, False, packed_stat), # current tree details
1295
afile_entry = ('', 'afile', 'afile-id'), [
1296
('f', 'sha1value', 34, False, packed_stat), # current tree details
1298
dirblocks.append(('', [subdir_entry, afile_entry]))
1300
file_entry2 = ('subdir', '2file', '2file-id'), [
1301
('f', 'sha1value', 23, False, packed_stat), # current tree details
1303
dirblocks.append(('subdir', [file_entry2]))
1304
state = dirstate.DirState.initialize('dirstate')
1306
state._set_data([], dirblocks)
1307
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1309
self.assertEqual(expected_entries, list(state._iter_entries()))
1314
class TestGetBlockRowIndex(TestCaseWithDirState):
1316
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1317
file_present, state, dirname, basename, tree_index):
1318
self.assertEqual((block_index, row_index, dir_present, file_present),
1319
state._get_block_entry_index(dirname, basename, tree_index))
1321
block = state._dirblocks[block_index]
1322
self.assertEqual(dirname, block[0])
1323
if dir_present and file_present:
1324
row = state._dirblocks[block_index][1][row_index]
1325
self.assertEqual(dirname, row[0][0])
1326
self.assertEqual(basename, row[0][1])
1328
def test_simple_structure(self):
1329
state = self.create_dirstate_with_root_and_subdir()
1330
self.addCleanup(state.unlock)
1331
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1332
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1333
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1334
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1335
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1338
def test_complex_structure_exists(self):
1339
state = self.create_complex_dirstate()
1340
self.addCleanup(state.unlock)
1341
# Make sure we can find everything that exists
1342
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1343
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1344
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1345
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1346
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1347
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1348
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1349
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1350
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1351
'b', 'h\xc3\xa5', 0)
1353
def test_complex_structure_missing(self):
1354
state = self.create_complex_dirstate()
1355
self.addCleanup(state.unlock)
1356
# Make sure things would be inserted in the right locations
1357
# '_' comes before 'a'
1358
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1359
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1360
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1361
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1363
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1364
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1365
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1366
# This would be inserted between a/ and b/
1367
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1369
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1372
class TestGetEntry(TestCaseWithDirState):
1374
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1375
"""Check that the right entry is returned for a request to getEntry."""
1376
entry = state._get_entry(index, path_utf8=path)
1378
self.assertEqual((None, None), entry)
1381
self.assertEqual((dirname, basename, file_id), cur[:3])
1383
def test_simple_structure(self):
1384
state = self.create_dirstate_with_root_and_subdir()
1385
self.addCleanup(state.unlock)
1386
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1387
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1388
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1389
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1390
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1392
def test_complex_structure_exists(self):
1393
state = self.create_complex_dirstate()
1394
self.addCleanup(state.unlock)
1395
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1396
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1397
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1398
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1399
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1400
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1401
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1402
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1403
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1406
def test_complex_structure_missing(self):
1407
state = self.create_complex_dirstate()
1408
self.addCleanup(state.unlock)
1409
self.assertEntryEqual(None, None, None, state, '_', 0)
1410
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1411
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1412
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1414
def test_get_entry_uninitialized(self):
1415
"""Calling get_entry will load data if it needs to"""
1416
state = self.create_dirstate_with_root()
1422
state = dirstate.DirState.on_file('dirstate')
1425
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1426
state._header_state)
1427
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1428
state._dirblock_state)
1429
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1434
class TestIterChildEntries(TestCaseWithDirState):
1436
def create_dirstate_with_two_trees(self):
1437
"""This dirstate contains multiple files and directories.
1447
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1449
Notice that a/e is an empty directory.
1451
There is one parent tree, which has the same shape with the following variations:
1452
b/g in the parent is gone.
1453
b/h in the parent has a different id
1454
b/i is new in the parent
1455
c is renamed to b/j in the parent
1457
:return: The dirstate, still write-locked.
1459
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1460
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1461
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1462
root_entry = ('', '', 'a-root-value'), [
1463
('d', '', 0, False, packed_stat),
1464
('d', '', 0, False, 'parent-revid'),
1466
a_entry = ('', 'a', 'a-dir'), [
1467
('d', '', 0, False, packed_stat),
1468
('d', '', 0, False, 'parent-revid'),
1470
b_entry = ('', 'b', 'b-dir'), [
1471
('d', '', 0, False, packed_stat),
1472
('d', '', 0, False, 'parent-revid'),
1474
c_entry = ('', 'c', 'c-file'), [
1475
('f', null_sha, 10, False, packed_stat),
1476
('r', 'b/j', 0, False, ''),
1478
d_entry = ('', 'd', 'd-file'), [
1479
('f', null_sha, 20, False, packed_stat),
1480
('f', 'd', 20, False, 'parent-revid'),
1482
e_entry = ('a', 'e', 'e-dir'), [
1483
('d', '', 0, False, packed_stat),
1484
('d', '', 0, False, 'parent-revid'),
1486
f_entry = ('a', 'f', 'f-file'), [
1487
('f', null_sha, 30, False, packed_stat),
1488
('f', 'f', 20, False, 'parent-revid'),
1490
g_entry = ('b', 'g', 'g-file'), [
1491
('f', null_sha, 30, False, packed_stat),
1492
NULL_PARENT_DETAILS,
1494
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1495
('f', null_sha, 40, False, packed_stat),
1496
NULL_PARENT_DETAILS,
1498
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1499
NULL_PARENT_DETAILS,
1500
('f', 'h', 20, False, 'parent-revid'),
1502
i_entry = ('b', 'i', 'i-file'), [
1503
NULL_PARENT_DETAILS,
1504
('f', 'h', 20, False, 'parent-revid'),
1506
j_entry = ('b', 'j', 'c-file'), [
1507
('r', 'c', 0, False, ''),
1508
('f', 'j', 20, False, 'parent-revid'),
1511
dirblocks.append(('', [root_entry]))
1512
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1513
dirblocks.append(('a', [e_entry, f_entry]))
1514
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1515
state = dirstate.DirState.initialize('dirstate')
1518
state._set_data(['parent'], dirblocks)
1522
return state, dirblocks
1524
def test_iter_children_b(self):
1525
state, dirblocks = self.create_dirstate_with_two_trees()
1526
self.addCleanup(state.unlock)
1527
expected_result = []
1528
expected_result.append(dirblocks[3][1][2]) # h2
1529
expected_result.append(dirblocks[3][1][3]) # i
1530
expected_result.append(dirblocks[3][1][4]) # j
1531
self.assertEqual(expected_result,
1532
list(state._iter_child_entries(1, 'b')))
1534
def test_iter_child_root(self):
1535
state, dirblocks = self.create_dirstate_with_two_trees()
1536
self.addCleanup(state.unlock)
1537
expected_result = []
1538
expected_result.append(dirblocks[1][1][0]) # a
1539
expected_result.append(dirblocks[1][1][1]) # b
1540
expected_result.append(dirblocks[1][1][3]) # d
1541
expected_result.append(dirblocks[2][1][0]) # e
1542
expected_result.append(dirblocks[2][1][1]) # f
1543
expected_result.append(dirblocks[3][1][2]) # h2
1544
expected_result.append(dirblocks[3][1][3]) # i
1545
expected_result.append(dirblocks[3][1][4]) # j
1546
self.assertEqual(expected_result,
1547
list(state._iter_child_entries(1, '')))
1550
class TestDirstateSortOrder(TestCaseWithTransport):
1551
"""Test that DirState adds entries in the right order."""
1553
def test_add_sorting(self):
1554
"""Add entries in lexicographical order, we get path sorted order.
1556
This tests it to a depth of 4, to make sure we don't just get it right
1557
at a single depth. 'a/a' should come before 'a-a', even though it
1558
doesn't lexicographically.
1560
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1561
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1564
state = dirstate.DirState.initialize('dirstate')
1565
self.addCleanup(state.unlock)
1567
fake_stat = os.stat('dirstate')
1569
d_id = d.replace('/', '_')+'-id'
1570
file_path = d + '/f'
1571
file_id = file_path.replace('/', '_')+'-id'
1572
state.add(d, d_id, 'directory', fake_stat, null_sha)
1573
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1575
expected = ['', '', 'a',
1576
'a/a', 'a/a/a', 'a/a/a/a',
1577
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1579
split = lambda p:p.split('/')
1580
self.assertEqual(sorted(expected, key=split), expected)
1581
dirblock_names = [d[0] for d in state._dirblocks]
1582
self.assertEqual(expected, dirblock_names)
1584
def test_set_parent_trees_correct_order(self):
1585
"""After calling set_parent_trees() we should maintain the order."""
1586
dirs = ['a', 'a-a', 'a/a']
1588
state = dirstate.DirState.initialize('dirstate')
1589
self.addCleanup(state.unlock)
1591
fake_stat = os.stat('dirstate')
1593
d_id = d.replace('/', '_')+'-id'
1594
file_path = d + '/f'
1595
file_id = file_path.replace('/', '_')+'-id'
1596
state.add(d, d_id, 'directory', fake_stat, null_sha)
1597
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1599
expected = ['', '', 'a', 'a/a', 'a-a']
1600
dirblock_names = [d[0] for d in state._dirblocks]
1601
self.assertEqual(expected, dirblock_names)
1603
# *really* cheesy way to just get an empty tree
1604
repo = self.make_repository('repo')
1605
empty_tree = repo.revision_tree(None)
1606
state.set_parent_trees([('null:', empty_tree)], [])
1608
dirblock_names = [d[0] for d in state._dirblocks]
1609
self.assertEqual(expected, dirblock_names)
1612
class InstrumentedDirState(dirstate.DirState):
1613
"""An DirState with instrumented sha1 functionality."""
1615
def __init__(self, path):
1616
super(InstrumentedDirState, self).__init__(path)
1617
self._time_offset = 0
1619
# member is dynamically set in DirState.__init__ to turn on trace
1620
self._sha1_file = self._sha1_file_and_log
1622
def _sha_cutoff_time(self):
1623
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1624
self._cutoff_time = timestamp + self._time_offset
1626
def _sha1_file_and_log(self, abspath):
1627
self._log.append(('sha1', abspath))
1628
return osutils.sha_file_by_name(abspath)
1630
def _read_link(self, abspath, old_link):
1631
self._log.append(('read_link', abspath, old_link))
1632
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1634
def _lstat(self, abspath, entry):
1635
self._log.append(('lstat', abspath))
1636
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1638
def _is_executable(self, mode, old_executable):
1639
self._log.append(('is_exec', mode, old_executable))
1640
return super(InstrumentedDirState, self)._is_executable(mode,
1643
def adjust_time(self, secs):
1644
"""Move the clock forward or back.
1646
:param secs: The amount to adjust the clock by. Positive values make it
1647
seem as if we are in the future, negative values make it seem like we
1650
self._time_offset += secs
1651
self._cutoff_time = None
1654
class _FakeStat(object):
1655
"""A class with the same attributes as a real stat result."""
1657
def __init__(self, size, mtime, ctime, dev, ino, mode):
1659
self.st_mtime = mtime
1660
self.st_ctime = ctime
1666
class TestUpdateEntry(TestCaseWithDirState):
1667
"""Test the DirState.update_entry functions"""
1669
def get_state_with_a(self):
1670
"""Create a DirState tracking a single object named 'a'"""
1671
state = InstrumentedDirState.initialize('dirstate')
1672
self.addCleanup(state.unlock)
1673
state.add('a', 'a-id', 'file', None, '')
1674
entry = state._get_entry(0, path_utf8='a')
1677
def test_update_entry(self):
1678
state, entry = self.get_state_with_a()
1679
self.build_tree(['a'])
1680
# Add one where we don't provide the stat or sha already
1681
self.assertEqual(('', 'a', 'a-id'), entry[0])
1682
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1684
# Flush the buffers to disk
1686
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1687
state._dirblock_state)
1689
stat_value = os.lstat('a')
1690
packed_stat = dirstate.pack_stat(stat_value)
1691
link_or_sha1 = state.update_entry(entry, abspath='a',
1692
stat_value=stat_value)
1693
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1696
# The dirblock entry should not cache the file's sha1
1697
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1699
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1700
state._dirblock_state)
1701
mode = stat_value.st_mode
1702
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1705
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1706
state._dirblock_state)
1708
# If we do it again right away, we don't know if the file has changed
1709
# so we will re-read the file. Roll the clock back so the file is
1710
# guaranteed to look too new.
1711
state.adjust_time(-10)
1713
link_or_sha1 = state.update_entry(entry, abspath='a',
1714
stat_value=stat_value)
1715
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1716
('sha1', 'a'), ('is_exec', mode, False),
1718
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1720
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1721
state._dirblock_state)
1722
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1726
# However, if we move the clock forward so the file is considered
1727
# "stable", it should just cache the value.
1728
state.adjust_time(+20)
1729
link_or_sha1 = state.update_entry(entry, abspath='a',
1730
stat_value=stat_value)
1731
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1733
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1734
('sha1', 'a'), ('is_exec', mode, False),
1735
('sha1', 'a'), ('is_exec', mode, False),
1737
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1740
# Subsequent calls will just return the cached value
1741
link_or_sha1 = state.update_entry(entry, abspath='a',
1742
stat_value=stat_value)
1743
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1745
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1746
('sha1', 'a'), ('is_exec', mode, False),
1747
('sha1', 'a'), ('is_exec', mode, False),
1749
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1752
def test_update_entry_symlink(self):
1753
"""Update entry should read symlinks."""
1754
self.requireFeature(SymlinkFeature)
1755
state, entry = self.get_state_with_a()
1757
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1758
state._dirblock_state)
1759
os.symlink('target', 'a')
1761
state.adjust_time(-10) # Make the symlink look new
1762
stat_value = os.lstat('a')
1763
packed_stat = dirstate.pack_stat(stat_value)
1764
link_or_sha1 = state.update_entry(entry, abspath='a',
1765
stat_value=stat_value)
1766
self.assertEqual('target', link_or_sha1)
1767
self.assertEqual([('read_link', 'a', '')], state._log)
1768
# Dirblock is not updated (the link is too new)
1769
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1771
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1772
state._dirblock_state)
1774
# Because the stat_value looks new, we should re-read the target
1775
link_or_sha1 = state.update_entry(entry, abspath='a',
1776
stat_value=stat_value)
1777
self.assertEqual('target', link_or_sha1)
1778
self.assertEqual([('read_link', 'a', ''),
1779
('read_link', 'a', ''),
1781
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1783
state.adjust_time(+20) # Skip into the future, all files look old
1784
link_or_sha1 = state.update_entry(entry, abspath='a',
1785
stat_value=stat_value)
1786
self.assertEqual('target', link_or_sha1)
1787
# We need to re-read the link because only now can we cache it
1788
self.assertEqual([('read_link', 'a', ''),
1789
('read_link', 'a', ''),
1790
('read_link', 'a', ''),
1792
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1795
# Another call won't re-read the link
1796
self.assertEqual([('read_link', 'a', ''),
1797
('read_link', 'a', ''),
1798
('read_link', 'a', ''),
1800
link_or_sha1 = state.update_entry(entry, abspath='a',
1801
stat_value=stat_value)
1802
self.assertEqual('target', link_or_sha1)
1803
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1806
def do_update_entry(self, state, entry, abspath):
1807
stat_value = os.lstat(abspath)
1808
return state.update_entry(entry, abspath, stat_value)
1810
def test_update_entry_dir(self):
1811
state, entry = self.get_state_with_a()
1812
self.build_tree(['a/'])
1813
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1815
def test_update_entry_dir_unchanged(self):
1816
state, entry = self.get_state_with_a()
1817
self.build_tree(['a/'])
1818
state.adjust_time(+20)
1819
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1820
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1821
state._dirblock_state)
1823
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1824
state._dirblock_state)
1825
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1826
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1827
state._dirblock_state)
1829
def test_update_entry_file_unchanged(self):
1830
state, entry = self.get_state_with_a()
1831
self.build_tree(['a'])
1832
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1833
state.adjust_time(+20)
1834
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1835
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1836
state._dirblock_state)
1838
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1839
state._dirblock_state)
1840
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1841
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1842
state._dirblock_state)
1844
def create_and_test_file(self, state, entry):
1845
"""Create a file at 'a' and verify the state finds it.
1847
The state should already be versioning *something* at 'a'. This makes
1848
sure that state.update_entry recognizes it as a file.
1850
self.build_tree(['a'])
1851
stat_value = os.lstat('a')
1852
packed_stat = dirstate.pack_stat(stat_value)
1854
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1855
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1857
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1861
def create_and_test_dir(self, state, entry):
1862
"""Create a directory at 'a' and verify the state finds it.
1864
The state should already be versioning *something* at 'a'. This makes
1865
sure that state.update_entry recognizes it as a directory.
1867
self.build_tree(['a/'])
1868
stat_value = os.lstat('a')
1869
packed_stat = dirstate.pack_stat(stat_value)
1871
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1872
self.assertIs(None, link_or_sha1)
1873
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1877
def create_and_test_symlink(self, state, entry):
1878
"""Create a symlink at 'a' and verify the state finds it.
1880
The state should already be versioning *something* at 'a'. This makes
1881
sure that state.update_entry recognizes it as a symlink.
1883
This should not be called if this platform does not have symlink
1886
# caller should care about skipping test on platforms without symlinks
1887
os.symlink('path/to/foo', 'a')
1889
stat_value = os.lstat('a')
1890
packed_stat = dirstate.pack_stat(stat_value)
1892
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1893
self.assertEqual('path/to/foo', link_or_sha1)
1894
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1898
def test_update_file_to_dir(self):
1899
"""If a file changes to a directory we return None for the sha.
1900
We also update the inventory record.
1902
state, entry = self.get_state_with_a()
1903
# The file sha1 won't be cached unless the file is old
1904
state.adjust_time(+10)
1905
self.create_and_test_file(state, entry)
1907
self.create_and_test_dir(state, entry)
1909
def test_update_file_to_symlink(self):
1910
"""File becomes a symlink"""
1911
self.requireFeature(SymlinkFeature)
1912
state, entry = self.get_state_with_a()
1913
# The file sha1 won't be cached unless the file is old
1914
state.adjust_time(+10)
1915
self.create_and_test_file(state, entry)
1917
self.create_and_test_symlink(state, entry)
1919
def test_update_dir_to_file(self):
1920
"""Directory becoming a file updates the entry."""
1921
state, entry = self.get_state_with_a()
1922
# The file sha1 won't be cached unless the file is old
1923
state.adjust_time(+10)
1924
self.create_and_test_dir(state, entry)
1926
self.create_and_test_file(state, entry)
1928
def test_update_dir_to_symlink(self):
1929
"""Directory becomes a symlink"""
1930
self.requireFeature(SymlinkFeature)
1931
state, entry = self.get_state_with_a()
1932
# The symlink target won't be cached if it isn't old
1933
state.adjust_time(+10)
1934
self.create_and_test_dir(state, entry)
1936
self.create_and_test_symlink(state, entry)
1938
def test_update_symlink_to_file(self):
1939
"""Symlink becomes a file"""
1940
self.requireFeature(SymlinkFeature)
1941
state, entry = self.get_state_with_a()
1942
# The symlink and file info won't be cached unless old
1943
state.adjust_time(+10)
1944
self.create_and_test_symlink(state, entry)
1946
self.create_and_test_file(state, entry)
1948
def test_update_symlink_to_dir(self):
1949
"""Symlink becomes a directory"""
1950
self.requireFeature(SymlinkFeature)
1951
state, entry = self.get_state_with_a()
1952
# The symlink target won't be cached if it isn't old
1953
state.adjust_time(+10)
1954
self.create_and_test_symlink(state, entry)
1956
self.create_and_test_dir(state, entry)
1958
def test__is_executable_win32(self):
1959
state, entry = self.get_state_with_a()
1960
self.build_tree(['a'])
1962
# Make sure we are using the win32 implementation of _is_executable
1963
state._is_executable = state._is_executable_win32
1965
# The file on disk is not executable, but we are marking it as though
1966
# it is. With _is_executable_win32 we ignore what is on disk.
1967
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1969
stat_value = os.lstat('a')
1970
packed_stat = dirstate.pack_stat(stat_value)
1972
state.adjust_time(-10) # Make sure everything is new
1973
state.update_entry(entry, abspath='a', stat_value=stat_value)
1975
# The row is updated, but the executable bit stays set.
1976
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1979
# Make the disk object look old enough to cache
1980
state.adjust_time(+20)
1981
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1982
state.update_entry(entry, abspath='a', stat_value=stat_value)
1983
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1986
class TestPackStat(TestCaseWithTransport):
1988
def assertPackStat(self, expected, stat_value):
1989
"""Check the packed and serialized form of a stat value."""
1990
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1992
def test_pack_stat_int(self):
1993
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1994
# Make sure that all parameters have an impact on the packed stat.
1995
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1998
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1999
st.st_mtime = 1172758620
2001
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
2002
st.st_ctime = 1172758630
2004
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2007
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
2008
st.st_ino = 6499540L
2010
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
2011
st.st_mode = 0100744
2013
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
2015
def test_pack_stat_float(self):
2016
"""On some platforms mtime and ctime are floats.
2018
Make sure we don't get warnings or errors, and that we ignore changes <
2021
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
2022
777L, 6499538L, 0100644)
2023
# These should all be the same as the integer counterparts
2024
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
2025
st.st_mtime = 1172758620.0
2027
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
2028
st.st_ctime = 1172758630.0
2030
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2031
# fractional seconds are discarded, so no change from above
2032
st.st_mtime = 1172758620.453
2033
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2034
st.st_ctime = 1172758630.228
2035
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2038
class TestBisect(TestCaseWithDirState):
2039
"""Test the ability to bisect into the disk format."""
2041
def assertBisect(self, expected_map, map_keys, state, paths):
2042
"""Assert that bisecting for paths returns the right result.
2044
:param expected_map: A map from key => entry value
2045
:param map_keys: The keys to expect for each path
2046
:param state: The DirState object.
2047
:param paths: A list of paths, these will automatically be split into
2048
(dir, name) tuples, and sorted according to how _bisect
2051
result = state._bisect(paths)
2052
# For now, results are just returned in whatever order we read them.
2053
# We could sort by (dir, name, file_id) or something like that, but in
2054
# the end it would still be fairly arbitrary, and we don't want the
2055
# extra overhead if we can avoid it. So sort everything to make sure
2057
assert len(map_keys) == len(paths)
2059
for path, keys in zip(paths, map_keys):
2061
# This should not be present in the output
2063
expected[path] = sorted(expected_map[k] for k in keys)
2065
# The returned values are just arranged randomly based on when they
2066
# were read, for testing, make sure it is properly sorted.
2070
self.assertEqual(expected, result)
2072
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
2073
"""Assert that bisecting for dirbblocks returns the right result.
2075
:param expected_map: A map from key => expected values
2076
:param map_keys: A nested list of paths we expect to be returned.
2077
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
2078
:param state: The DirState object.
2079
:param paths: A list of directories
2081
result = state._bisect_dirblocks(paths)
2082
assert len(map_keys) == len(paths)
2085
for path, keys in zip(paths, map_keys):
2087
# This should not be present in the output
2089
expected[path] = sorted(expected_map[k] for k in keys)
2093
self.assertEqual(expected, result)
2095
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
2096
"""Assert the return value of a recursive bisection.
2098
:param expected_map: A map from key => entry value
2099
:param map_keys: A list of paths we expect to be returned.
2100
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
2101
:param state: The DirState object.
2102
:param paths: A list of files and directories. It will be broken up
2103
into (dir, name) pairs and sorted before calling _bisect_recursive.
2106
for key in map_keys:
2107
entry = expected_map[key]
2108
dir_name_id, trees_info = entry
2109
expected[dir_name_id] = trees_info
2111
result = state._bisect_recursive(paths)
2113
self.assertEqual(expected, result)
2115
def test_bisect_each(self):
2116
"""Find a single record using bisect."""
2117
tree, state, expected = self.create_basic_dirstate()
2119
# Bisect should return the rows for the specified files.
2120
self.assertBisect(expected, [['']], state, [''])
2121
self.assertBisect(expected, [['a']], state, ['a'])
2122
self.assertBisect(expected, [['b']], state, ['b'])
2123
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2124
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2125
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2126
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2127
self.assertBisect(expected, [['f']], state, ['f'])
2129
def test_bisect_multi(self):
2130
"""Bisect can be used to find multiple records at the same time."""
2131
tree, state, expected = self.create_basic_dirstate()
2132
# Bisect should be capable of finding multiple entries at the same time
2133
self.assertBisect(expected, [['a'], ['b'], ['f']],
2134
state, ['a', 'b', 'f'])
2135
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2136
state, ['f', 'b/d', 'b/d/e'])
2137
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2138
state, ['b', 'b-c', 'b/c'])
2140
def test_bisect_one_page(self):
2141
"""Test bisect when there is only 1 page to read"""
2142
tree, state, expected = self.create_basic_dirstate()
2143
state._bisect_page_size = 5000
2144
self.assertBisect(expected,[['']], state, [''])
2145
self.assertBisect(expected,[['a']], state, ['a'])
2146
self.assertBisect(expected,[['b']], state, ['b'])
2147
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2148
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2149
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2150
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2151
self.assertBisect(expected,[['f']], state, ['f'])
2152
self.assertBisect(expected,[['a'], ['b'], ['f']],
2153
state, ['a', 'b', 'f'])
2154
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2155
state, ['b/d', 'b/d/e', 'f'])
2156
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2157
state, ['b', 'b/c', 'b-c'])
2159
def test_bisect_duplicate_paths(self):
2160
"""When bisecting for a path, handle multiple entries."""
2161
tree, state, expected = self.create_duplicated_dirstate()
2163
# Now make sure that both records are properly returned.
2164
self.assertBisect(expected, [['']], state, [''])
2165
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2166
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2167
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2168
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2169
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2171
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2172
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2174
def test_bisect_page_size_too_small(self):
2175
"""If the page size is too small, we will auto increase it."""
2176
tree, state, expected = self.create_basic_dirstate()
2177
state._bisect_page_size = 50
2178
self.assertBisect(expected, [None], state, ['b/e'])
2179
self.assertBisect(expected, [['a']], state, ['a'])
2180
self.assertBisect(expected, [['b']], state, ['b'])
2181
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2182
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2183
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2184
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2185
self.assertBisect(expected, [['f']], state, ['f'])
2187
def test_bisect_missing(self):
2188
"""Test that bisect return None if it cannot find a path."""
2189
tree, state, expected = self.create_basic_dirstate()
2190
self.assertBisect(expected, [None], state, ['foo'])
2191
self.assertBisect(expected, [None], state, ['b/foo'])
2192
self.assertBisect(expected, [None], state, ['bar/foo'])
2193
self.assertBisect(expected, [None], state, ['b-c/foo'])
2195
self.assertBisect(expected, [['a'], None, ['b/d']],
2196
state, ['a', 'foo', 'b/d'])
2198
def test_bisect_rename(self):
2199
"""Check that we find a renamed row."""
2200
tree, state, expected = self.create_renamed_dirstate()
2202
# Search for the pre and post renamed entries
2203
self.assertBisect(expected, [['a']], state, ['a'])
2204
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2205
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2206
self.assertBisect(expected, [['h']], state, ['h'])
2208
# What about b/d/e? shouldn't that also get 2 directory entries?
2209
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2210
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2212
def test_bisect_dirblocks(self):
2213
tree, state, expected = self.create_duplicated_dirstate()
2214
self.assertBisectDirBlocks(expected,
2215
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2217
self.assertBisectDirBlocks(expected,
2218
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2219
self.assertBisectDirBlocks(expected,
2220
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2221
self.assertBisectDirBlocks(expected,
2222
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2223
['b/c', 'b/c2', 'b/d', 'b/d2'],
2224
['b/d/e', 'b/d/e2'],
2225
], state, ['', 'b', 'b/d'])
2227
def test_bisect_dirblocks_missing(self):
2228
tree, state, expected = self.create_basic_dirstate()
2229
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2230
state, ['b/d', 'b/e'])
2231
# Files don't show up in this search
2232
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2233
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2234
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2235
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2236
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2238
def test_bisect_recursive_each(self):
2239
tree, state, expected = self.create_basic_dirstate()
2240
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2241
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2242
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2243
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2244
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2246
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2248
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2252
def test_bisect_recursive_multiple(self):
2253
tree, state, expected = self.create_basic_dirstate()
2254
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2255
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2256
state, ['b/d', 'b/d/e'])
2258
def test_bisect_recursive_missing(self):
2259
tree, state, expected = self.create_basic_dirstate()
2260
self.assertBisectRecursive(expected, [], state, ['d'])
2261
self.assertBisectRecursive(expected, [], state, ['b/e'])
2262
self.assertBisectRecursive(expected, [], state, ['g'])
2263
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2265
def test_bisect_recursive_renamed(self):
2266
tree, state, expected = self.create_renamed_dirstate()
2268
# Looking for either renamed item should find the other
2269
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2270
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2271
# Looking in the containing directory should find the rename target,
2272
# and anything in a subdir of the renamed target.
2273
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2274
'b/d/e', 'b/g', 'h', 'h/e'],
2278
class TestDirstateValidation(TestCaseWithDirState):
2280
def test_validate_correct_dirstate(self):
2281
state = self.create_complex_dirstate()
2284
# and make sure we can also validate with a read lock
2291
def test_dirblock_not_sorted(self):
2292
tree, state, expected = self.create_renamed_dirstate()
2293
state._read_dirblocks_if_needed()
2294
last_dirblock = state._dirblocks[-1]
2295
# we're appending to the dirblock, but this name comes before some of
2296
# the existing names; that's wrong
2297
last_dirblock[1].append(
2298
(('h', 'aaaa', 'a-id'),
2299
[('a', '', 0, False, ''),
2300
('a', '', 0, False, '')]))
2301
e = self.assertRaises(AssertionError,
2303
self.assertContainsRe(str(e), 'not sorted')
2305
def test_dirblock_name_mismatch(self):
2306
tree, state, expected = self.create_renamed_dirstate()
2307
state._read_dirblocks_if_needed()
2308
last_dirblock = state._dirblocks[-1]
2309
# add an entry with the wrong directory name
2310
last_dirblock[1].append(
2312
[('a', '', 0, False, ''),
2313
('a', '', 0, False, '')]))
2314
e = self.assertRaises(AssertionError,
2316
self.assertContainsRe(str(e),
2317
"doesn't match directory name")
2319
def test_dirblock_missing_rename(self):
2320
tree, state, expected = self.create_renamed_dirstate()
2321
state._read_dirblocks_if_needed()
2322
last_dirblock = state._dirblocks[-1]
2323
# make another entry for a-id, without a correct 'r' pointer to
2324
# the real occurrence in the working tree
2325
last_dirblock[1].append(
2326
(('h', 'z', 'a-id'),
2327
[('a', '', 0, False, ''),
2328
('a', '', 0, False, '')]))
2329
e = self.assertRaises(AssertionError,
2331
self.assertContainsRe(str(e),
2332
'file a-id is absent in row')
2335
class TestDirstateTreeReference(TestCaseWithDirState):
2337
def test_reference_revision_is_none(self):
2338
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2339
subtree = self.make_branch_and_tree('tree/subtree',
2340
format='dirstate-with-subtree')
2341
subtree.set_root_id('subtree')
2342
tree.add_reference(subtree)
2344
state = dirstate.DirState.from_tree(tree, 'dirstate')
2345
key = ('', 'subtree', 'subtree')
2346
expected = ('', [(key,
2347
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2350
self.assertEqual(expected, state._find_block(key))
2355
class TestDiscardMergeParents(TestCaseWithDirState):
2357
def test_discard_no_parents(self):
2358
# This should be a no-op
2359
state = self.create_empty_dirstate()
2360
self.addCleanup(state.unlock)
2361
state._discard_merge_parents()
2364
def test_discard_one_parent(self):
2366
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2367
root_entry_direntry = ('', '', 'a-root-value'), [
2368
('d', '', 0, False, packed_stat),
2369
('d', '', 0, False, packed_stat),
2372
dirblocks.append(('', [root_entry_direntry]))
2373
dirblocks.append(('', []))
2375
state = self.create_empty_dirstate()
2376
self.addCleanup(state.unlock)
2377
state._set_data(['parent-id'], dirblocks[:])
2380
state._discard_merge_parents()
2382
self.assertEqual(dirblocks, state._dirblocks)
2384
def test_discard_simple(self):
2386
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2387
root_entry_direntry = ('', '', 'a-root-value'), [
2388
('d', '', 0, False, packed_stat),
2389
('d', '', 0, False, packed_stat),
2390
('d', '', 0, False, packed_stat),
2392
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2393
('d', '', 0, False, packed_stat),
2394
('d', '', 0, False, packed_stat),
2397
dirblocks.append(('', [root_entry_direntry]))
2398
dirblocks.append(('', []))
2400
state = self.create_empty_dirstate()
2401
self.addCleanup(state.unlock)
2402
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2405
# This should strip of the extra column
2406
state._discard_merge_parents()
2408
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2409
self.assertEqual(expected_dirblocks, state._dirblocks)
2411
def test_discard_absent(self):
2412
"""If entries are only in a merge, discard should remove the entries"""
2413
null_stat = dirstate.DirState.NULLSTAT
2414
present_dir = ('d', '', 0, False, null_stat)
2415
present_file = ('f', '', 0, False, null_stat)
2416
absent = dirstate.DirState.NULL_PARENT_DETAILS
2417
root_key = ('', '', 'a-root-value')
2418
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2419
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2420
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2421
('', [(file_in_merged_key,
2422
[absent, absent, present_file]),
2424
[present_file, present_file, present_file]),
2428
state = self.create_empty_dirstate()
2429
self.addCleanup(state.unlock)
2430
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2433
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2434
('', [(file_in_root_key,
2435
[present_file, present_file]),
2438
state._discard_merge_parents()
2440
self.assertEqual(exp_dirblocks, state._dirblocks)
2442
def test_discard_renamed(self):
2443
null_stat = dirstate.DirState.NULLSTAT
2444
present_dir = ('d', '', 0, False, null_stat)
2445
present_file = ('f', '', 0, False, null_stat)
2446
absent = dirstate.DirState.NULL_PARENT_DETAILS
2447
root_key = ('', '', 'a-root-value')
2448
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2449
# Renamed relative to parent
2450
file_rename_s_key = ('', 'file-s', 'b-file-id')
2451
file_rename_t_key = ('', 'file-t', 'b-file-id')
2452
# And one that is renamed between the parents, but absent in this
2453
key_in_1 = ('', 'file-in-1', 'c-file-id')
2454
key_in_2 = ('', 'file-in-2', 'c-file-id')
2457
('', [(root_key, [present_dir, present_dir, present_dir])]),
2459
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2461
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2463
[present_file, present_file, present_file]),
2465
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2467
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2471
('', [(root_key, [present_dir, present_dir])]),
2472
('', [(key_in_1, [absent, present_file]),
2473
(file_in_root_key, [present_file, present_file]),
2474
(file_rename_t_key, [present_file, absent]),
2477
state = self.create_empty_dirstate()
2478
self.addCleanup(state.unlock)
2479
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2482
state._discard_merge_parents()
2484
self.assertEqual(exp_dirblocks, state._dirblocks)
2486
def test_discard_all_subdir(self):
2487
null_stat = dirstate.DirState.NULLSTAT
2488
present_dir = ('d', '', 0, False, null_stat)
2489
present_file = ('f', '', 0, False, null_stat)
2490
absent = dirstate.DirState.NULL_PARENT_DETAILS
2491
root_key = ('', '', 'a-root-value')
2492
subdir_key = ('', 'sub', 'dir-id')
2493
child1_key = ('sub', 'child1', 'child1-id')
2494
child2_key = ('sub', 'child2', 'child2-id')
2495
child3_key = ('sub', 'child3', 'child3-id')
2498
('', [(root_key, [present_dir, present_dir, present_dir])]),
2499
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2500
('sub', [(child1_key, [absent, absent, present_file]),
2501
(child2_key, [absent, absent, present_file]),
2502
(child3_key, [absent, absent, present_file]),
2506
('', [(root_key, [present_dir, present_dir])]),
2507
('', [(subdir_key, [present_dir, present_dir])]),
2510
state = self.create_empty_dirstate()
2511
self.addCleanup(state.unlock)
2512
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2515
state._discard_merge_parents()
2517
self.assertEqual(exp_dirblocks, state._dirblocks)