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."""
29
from bzrlib.memorytree import MemoryTree
30
from bzrlib.tests import (
33
TestCaseWithTransport,
39
# general checks for NOT_IN_MEMORY error conditions.
40
# set_path_id on a NOT_IN_MEMORY dirstate
41
# set_path_id unicode support
42
# set_path_id setting id of a path not root
43
# set_path_id setting id when there are parents without the id in the parents
44
# set_path_id setting id when there are parents with the id in the parents
45
# set_path_id setting id when state is not in memory
46
# set_path_id setting id when state is in memory unmodified
47
# set_path_id setting id when state is in memory modified
50
class TestCaseWithDirState(TestCaseWithTransport):
51
"""Helper functions for creating DirState objects with various content."""
53
def create_empty_dirstate(self):
54
"""Return a locked but empty dirstate"""
55
state = dirstate.DirState.initialize('dirstate')
58
def create_dirstate_with_root(self):
59
"""Return a write-locked state with a single root entry."""
60
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
61
root_entry_direntry = ('', '', 'a-root-value'), [
62
('d', '', 0, False, packed_stat),
65
dirblocks.append(('', [root_entry_direntry]))
66
dirblocks.append(('', []))
67
state = self.create_empty_dirstate()
69
state._set_data([], dirblocks)
76
def create_dirstate_with_root_and_subdir(self):
77
"""Return a locked DirState with a root and a subdir"""
78
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
79
subdir_entry = ('', 'subdir', 'subdir-id'), [
80
('d', '', 0, False, packed_stat),
82
state = self.create_dirstate_with_root()
84
dirblocks = list(state._dirblocks)
85
dirblocks[1][1].append(subdir_entry)
86
state._set_data([], dirblocks)
92
def create_complex_dirstate(self):
93
"""This dirstate contains multiple files and directories.
103
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
105
Notice that a/e is an empty directory.
107
:return: The dirstate, still write-locked.
109
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
110
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
111
root_entry = ('', '', 'a-root-value'), [
112
('d', '', 0, False, packed_stat),
114
a_entry = ('', 'a', 'a-dir'), [
115
('d', '', 0, False, packed_stat),
117
b_entry = ('', 'b', 'b-dir'), [
118
('d', '', 0, False, packed_stat),
120
c_entry = ('', 'c', 'c-file'), [
121
('f', null_sha, 10, False, packed_stat),
123
d_entry = ('', 'd', 'd-file'), [
124
('f', null_sha, 20, False, packed_stat),
126
e_entry = ('a', 'e', 'e-dir'), [
127
('d', '', 0, False, packed_stat),
129
f_entry = ('a', 'f', 'f-file'), [
130
('f', null_sha, 30, False, packed_stat),
132
g_entry = ('b', 'g', 'g-file'), [
133
('f', null_sha, 30, False, packed_stat),
135
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
136
('f', null_sha, 40, False, packed_stat),
139
dirblocks.append(('', [root_entry]))
140
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
141
dirblocks.append(('a', [e_entry, f_entry]))
142
dirblocks.append(('b', [g_entry, h_entry]))
143
state = dirstate.DirState.initialize('dirstate')
146
state._set_data([], dirblocks)
152
def check_state_with_reopen(self, expected_result, state):
153
"""Check that state has current state expected_result.
155
This will check the current state, open the file anew and check it
157
This function expects the current state to be locked for writing, and
158
will unlock it before re-opening.
159
This is required because we can't open a lock_read() while something
160
else has a lock_write().
161
write => mutually exclusive lock
164
# The state should already be write locked, since we just had to do
165
# some operation to get here.
166
self.assertTrue(state._lock_token is not None)
168
self.assertEqual(expected_result[0], state.get_parent_ids())
169
# there should be no ghosts in this tree.
170
self.assertEqual([], state.get_ghosts())
171
# there should be one fileid in this tree - the root of the tree.
172
self.assertEqual(expected_result[1], list(state._iter_entries()))
177
state = dirstate.DirState.on_file('dirstate')
180
self.assertEqual(expected_result[1], list(state._iter_entries()))
184
def create_basic_dirstate(self):
185
"""Create a dirstate with a few files and directories.
195
tree = self.make_branch_and_tree('tree')
196
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
197
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
198
self.build_tree(['tree/' + p for p in paths])
199
tree.set_root_id('TREE_ROOT')
200
tree.add([p.rstrip('/') for p in paths], file_ids)
201
tree.commit('initial', rev_id='rev-1')
202
revision_id = 'rev-1'
203
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
204
t = self.get_transport('tree')
205
a_text = t.get_bytes('a')
206
a_sha = osutils.sha_string(a_text)
208
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
209
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
210
c_text = t.get_bytes('b/c')
211
c_sha = osutils.sha_string(c_text)
213
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
214
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
215
e_text = t.get_bytes('b/d/e')
216
e_sha = osutils.sha_string(e_text)
218
b_c_text = t.get_bytes('b-c')
219
b_c_sha = osutils.sha_string(b_c_text)
220
b_c_len = len(b_c_text)
221
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
222
f_text = t.get_bytes('f')
223
f_sha = osutils.sha_string(f_text)
225
null_stat = dirstate.DirState.NULLSTAT
227
'':(('', '', 'TREE_ROOT'), [
228
('d', '', 0, False, null_stat),
229
('d', '', 0, False, revision_id),
231
'a':(('', 'a', 'a-id'), [
232
('f', '', 0, False, null_stat),
233
('f', a_sha, a_len, False, revision_id),
235
'b':(('', 'b', 'b-id'), [
236
('d', '', 0, False, null_stat),
237
('d', '', 0, False, revision_id),
239
'b/c':(('b', 'c', 'c-id'), [
240
('f', '', 0, False, null_stat),
241
('f', c_sha, c_len, False, revision_id),
243
'b/d':(('b', 'd', 'd-id'), [
244
('d', '', 0, False, null_stat),
245
('d', '', 0, False, revision_id),
247
'b/d/e':(('b/d', 'e', 'e-id'), [
248
('f', '', 0, False, null_stat),
249
('f', e_sha, e_len, False, revision_id),
251
'b-c':(('', 'b-c', 'b-c-id'), [
252
('f', '', 0, False, null_stat),
253
('f', b_c_sha, b_c_len, False, revision_id),
255
'f':(('', 'f', 'f-id'), [
256
('f', '', 0, False, null_stat),
257
('f', f_sha, f_len, False, revision_id),
260
state = dirstate.DirState.from_tree(tree, 'dirstate')
265
# Use a different object, to make sure nothing is pre-cached in memory.
266
state = dirstate.DirState.on_file('dirstate')
268
self.addCleanup(state.unlock)
269
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
270
state._dirblock_state)
271
# This is code is only really tested if we actually have to make more
272
# than one read, so set the page size to something smaller.
273
# We want it to contain about 2.2 records, so that we have a couple
274
# records that we can read per attempt
275
state._bisect_page_size = 200
276
return tree, state, expected
278
def create_duplicated_dirstate(self):
279
"""Create a dirstate with a deleted and added entries.
281
This grabs a basic_dirstate, and then removes and re adds every entry
284
tree, state, expected = self.create_basic_dirstate()
285
# Now we will just remove and add every file so we get an extra entry
286
# per entry. Unversion in reverse order so we handle subdirs
287
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
288
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
289
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
291
# Update the expected dictionary.
292
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
293
orig = expected[path]
295
# This record was deleted in the current tree
296
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
298
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
299
# And didn't exist in the basis tree
300
expected[path2] = (new_key, [orig[1][0],
301
dirstate.DirState.NULL_PARENT_DETAILS])
303
# We will replace the 'dirstate' file underneath 'state', but that is
304
# okay as lock as we unlock 'state' first.
307
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
313
# But we need to leave state in a read-lock because we already have
314
# a cleanup scheduled
316
return tree, state, expected
318
def create_renamed_dirstate(self):
319
"""Create a dirstate with a few internal renames.
321
This takes the basic dirstate, and moves the paths around.
323
tree, state, expected = self.create_basic_dirstate()
325
tree.rename_one('a', 'b/g')
327
tree.rename_one('b/d', 'h')
329
old_a = expected['a']
330
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
331
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
332
('r', 'a', 0, False, '')])
333
old_d = expected['b/d']
334
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
335
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
336
('r', 'b/d', 0, False, '')])
338
old_e = expected['b/d/e']
339
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
341
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
342
('r', 'b/d/e', 0, False, '')])
346
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
353
return tree, state, expected
356
class TestTreeToDirState(TestCaseWithDirState):
358
def test_empty_to_dirstate(self):
359
"""We should be able to create a dirstate for an empty tree."""
360
# There are no files on disk and no parents
361
tree = self.make_branch_and_tree('tree')
362
expected_result = ([], [
363
(('', '', tree.get_root_id()), # common details
364
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
366
state = dirstate.DirState.from_tree(tree, 'dirstate')
368
self.check_state_with_reopen(expected_result, state)
370
def test_1_parents_empty_to_dirstate(self):
371
# create a parent by doing a commit
372
tree = self.make_branch_and_tree('tree')
373
rev_id = tree.commit('first post').encode('utf8')
374
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
375
expected_result = ([rev_id], [
376
(('', '', tree.get_root_id()), # common details
377
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
378
('d', '', 0, False, rev_id), # first parent details
380
state = dirstate.DirState.from_tree(tree, 'dirstate')
381
self.check_state_with_reopen(expected_result, state)
388
def test_2_parents_empty_to_dirstate(self):
389
# create a parent by doing a commit
390
tree = self.make_branch_and_tree('tree')
391
rev_id = tree.commit('first post')
392
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
393
rev_id2 = tree2.commit('second post', allow_pointless=True)
394
tree.merge_from_branch(tree2.branch)
395
expected_result = ([rev_id, rev_id2], [
396
(('', '', tree.get_root_id()), # common details
397
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
398
('d', '', 0, False, rev_id), # first parent details
399
('d', '', 0, False, rev_id2), # second parent details
401
state = dirstate.DirState.from_tree(tree, 'dirstate')
402
self.check_state_with_reopen(expected_result, state)
409
def test_empty_unknowns_are_ignored_to_dirstate(self):
410
"""We should be able to create a dirstate for an empty tree."""
411
# There are no files on disk and no parents
412
tree = self.make_branch_and_tree('tree')
413
self.build_tree(['tree/unknown'])
414
expected_result = ([], [
415
(('', '', tree.get_root_id()), # common details
416
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
418
state = dirstate.DirState.from_tree(tree, 'dirstate')
419
self.check_state_with_reopen(expected_result, state)
421
def get_tree_with_a_file(self):
422
tree = self.make_branch_and_tree('tree')
423
self.build_tree(['tree/a file'])
424
tree.add('a file', 'a-file-id')
427
def test_non_empty_no_parents_to_dirstate(self):
428
"""We should be able to create a dirstate for an empty tree."""
429
# There are files on disk and no parents
430
tree = self.get_tree_with_a_file()
431
expected_result = ([], [
432
(('', '', tree.get_root_id()), # common details
433
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
435
(('', 'a file', 'a-file-id'), # common
436
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
439
state = dirstate.DirState.from_tree(tree, 'dirstate')
440
self.check_state_with_reopen(expected_result, state)
442
def test_1_parents_not_empty_to_dirstate(self):
443
# create a parent by doing a commit
444
tree = self.get_tree_with_a_file()
445
rev_id = tree.commit('first post').encode('utf8')
446
# change the current content to be different this will alter stat, sha
448
self.build_tree_contents([('tree/a file', 'new content\n')])
449
expected_result = ([rev_id], [
450
(('', '', tree.get_root_id()), # common details
451
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
452
('d', '', 0, False, rev_id), # first parent details
454
(('', 'a file', 'a-file-id'), # common
455
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
456
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
457
rev_id), # first parent
460
state = dirstate.DirState.from_tree(tree, 'dirstate')
461
self.check_state_with_reopen(expected_result, state)
463
def test_2_parents_not_empty_to_dirstate(self):
464
# create a parent by doing a commit
465
tree = self.get_tree_with_a_file()
466
rev_id = tree.commit('first post').encode('utf8')
467
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
468
# change the current content to be different this will alter stat, sha
470
self.build_tree_contents([('tree2/a file', 'merge content\n')])
471
rev_id2 = tree2.commit('second post').encode('utf8')
472
tree.merge_from_branch(tree2.branch)
473
# change the current content to be different this will alter stat, sha
474
# and length again, giving us three distinct values:
475
self.build_tree_contents([('tree/a file', 'new content\n')])
476
expected_result = ([rev_id, rev_id2], [
477
(('', '', tree.get_root_id()), # common details
478
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
479
('d', '', 0, False, rev_id), # first parent details
480
('d', '', 0, False, rev_id2), # second parent details
482
(('', 'a file', 'a-file-id'), # common
483
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
484
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
485
rev_id), # first parent
486
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
487
rev_id2), # second parent
490
state = dirstate.DirState.from_tree(tree, 'dirstate')
491
self.check_state_with_reopen(expected_result, state)
493
def test_colliding_fileids(self):
494
# test insertion of parents creating several entries at the same path.
495
# we used to have a bug where they could cause the dirstate to break
496
# its ordering invariants.
497
# create some trees to test from
500
tree = self.make_branch_and_tree('tree%d' % i)
501
self.build_tree(['tree%d/name' % i,])
502
tree.add(['name'], ['file-id%d' % i])
503
revision_id = 'revid-%d' % i
504
tree.commit('message', rev_id=revision_id)
505
parents.append((revision_id,
506
tree.branch.repository.revision_tree(revision_id)))
507
# now fold these trees into a dirstate
508
state = dirstate.DirState.initialize('dirstate')
510
state.set_parent_trees(parents, [])
516
class TestDirStateOnFile(TestCaseWithDirState):
518
def test_construct_with_path(self):
519
tree = self.make_branch_and_tree('tree')
520
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
521
# we want to be able to get the lines of the dirstate that we will
523
lines = state.get_lines()
525
self.build_tree_contents([('dirstate', ''.join(lines))])
527
# no parents, default tree content
528
expected_result = ([], [
529
(('', '', tree.get_root_id()), # common details
530
# current tree details, but new from_tree skips statting, it
531
# uses set_state_from_inventory, and thus depends on the
533
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
536
state = dirstate.DirState.on_file('dirstate')
537
state.lock_write() # check_state_with_reopen will save() and unlock it
538
self.check_state_with_reopen(expected_result, state)
540
def test_can_save_clean_on_file(self):
541
tree = self.make_branch_and_tree('tree')
542
state = dirstate.DirState.from_tree(tree, 'dirstate')
544
# doing a save should work here as there have been no changes.
546
# TODO: stat it and check it hasn't changed; may require waiting
547
# for the state accuracy window.
551
def test_can_save_in_read_lock(self):
552
self.build_tree(['a-file'])
553
state = dirstate.DirState.initialize('dirstate')
555
# No stat and no sha1 sum.
556
state.add('a-file', 'a-file-id', 'file', None, '')
561
# Now open in readonly mode
562
state = dirstate.DirState.on_file('dirstate')
565
entry = state._get_entry(0, path_utf8='a-file')
566
# The current sha1 sum should be empty
567
self.assertEqual('', entry[1][0][1])
568
# We should have a real entry.
569
self.assertNotEqual((None, None), entry)
570
# Make sure everything is old enough
571
state._sha_cutoff_time()
572
state._cutoff_time += 10
573
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
574
# We should have gotten a real sha1
575
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
578
# The dirblock has been updated
579
self.assertEqual(sha1sum, entry[1][0][1])
580
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
581
state._dirblock_state)
584
# Now, since we are the only one holding a lock, we should be able
585
# to save and have it written to disk
590
# Re-open the file, and ensure that the state has been updated.
591
state = dirstate.DirState.on_file('dirstate')
594
entry = state._get_entry(0, path_utf8='a-file')
595
self.assertEqual(sha1sum, entry[1][0][1])
599
def test_save_fails_quietly_if_locked(self):
600
"""If dirstate is locked, save will fail without complaining."""
601
self.build_tree(['a-file'])
602
state = dirstate.DirState.initialize('dirstate')
604
# No stat and no sha1 sum.
605
state.add('a-file', 'a-file-id', 'file', None, '')
610
state = dirstate.DirState.on_file('dirstate')
613
entry = state._get_entry(0, path_utf8='a-file')
614
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
615
# We should have gotten a real sha1
616
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
618
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
619
state._dirblock_state)
621
# Now, before we try to save, grab another dirstate, and take out a
623
# TODO: jam 20070315 Ideally this would be locked by another
624
# process. To make sure the file is really OS locked.
625
state2 = dirstate.DirState.on_file('dirstate')
628
# This won't actually write anything, because it couldn't grab
629
# a write lock. But it shouldn't raise an error, either.
630
# TODO: jam 20070315 We should probably distinguish between
631
# being dirty because of 'update_entry'. And dirty
632
# because of real modification. So that save() *does*
633
# raise a real error if it fails when we have real
641
# The file on disk should not be modified.
642
state = dirstate.DirState.on_file('dirstate')
645
entry = state._get_entry(0, path_utf8='a-file')
646
self.assertEqual('', entry[1][0][1])
650
def test_save_refuses_if_changes_aborted(self):
651
self.build_tree(['a-file', 'a-dir/'])
652
state = dirstate.DirState.initialize('dirstate')
654
# No stat and no sha1 sum.
655
state.add('a-file', 'a-file-id', 'file', None, '')
660
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
662
('', [(('', '', 'TREE_ROOT'),
663
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
664
('', [(('', 'a-file', 'a-file-id'),
665
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
668
state = dirstate.DirState.on_file('dirstate')
671
state._read_dirblocks_if_needed()
672
self.assertEqual(expected_blocks, state._dirblocks)
674
# Now modify the state, but mark it as inconsistent
675
state.add('a-dir', 'a-dir-id', 'directory', None, '')
676
state._changes_aborted = True
681
state = dirstate.DirState.on_file('dirstate')
684
state._read_dirblocks_if_needed()
685
self.assertEqual(expected_blocks, state._dirblocks)
690
class TestDirStateInitialize(TestCaseWithDirState):
692
def test_initialize(self):
693
expected_result = ([], [
694
(('', '', 'TREE_ROOT'), # common details
695
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
698
state = dirstate.DirState.initialize('dirstate')
700
self.assertIsInstance(state, dirstate.DirState)
701
lines = state.get_lines()
704
# On win32 you can't read from a locked file, even within the same
705
# process. So we have to unlock and release before we check the file
707
self.assertFileEqual(''.join(lines), 'dirstate')
708
state.lock_read() # check_state_with_reopen will unlock
709
self.check_state_with_reopen(expected_result, state)
712
class TestDirStateManipulations(TestCaseWithDirState):
714
def test_set_state_from_inventory_no_content_no_parents(self):
715
# setting the current inventory is a slow but important api to support.
716
tree1 = self.make_branch_and_memory_tree('tree1')
720
revid1 = tree1.commit('foo').encode('utf8')
721
root_id = tree1.get_root_id()
722
inv = tree1.inventory
725
expected_result = [], [
726
(('', '', root_id), [
727
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
728
state = dirstate.DirState.initialize('dirstate')
730
state.set_state_from_inventory(inv)
731
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
733
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
734
state._dirblock_state)
739
# This will unlock it
740
self.check_state_with_reopen(expected_result, state)
742
def test_set_state_from_inventory_preserves_hashcache(self):
743
# https://bugs.launchpad.net/bzr/+bug/146176
744
# set_state_from_inventory should preserve the stat and hash value for
745
# workingtree files that are not changed by the inventory.
747
tree = self.make_branch_and_tree('.')
748
# depends on the default format using dirstate...
751
# make a dirstate with some valid hashcache data
752
# file on disk, but that's not needed for this test
753
foo_contents = 'contents of foo'
754
self.build_tree_contents([('foo', foo_contents)])
755
tree.add('foo', 'foo-id')
757
foo_stat = os.stat('foo')
758
foo_packed = dirstate.pack_stat(foo_stat)
759
foo_sha = osutils.sha_string(foo_contents)
760
foo_size = len(foo_contents)
762
# should not be cached yet, because the file's too fresh
764
(('', 'foo', 'foo-id',),
765
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
766
tree._dirstate._get_entry(0, 'foo-id'))
767
# poke in some hashcache information - it wouldn't normally be
768
# stored because it's too fresh
769
tree._dirstate.update_minimal(
770
('', 'foo', 'foo-id'),
771
'f', False, foo_sha, foo_packed, foo_size, 'foo')
772
# now should be cached
774
(('', 'foo', 'foo-id',),
775
[('f', foo_sha, foo_size, False, foo_packed)]),
776
tree._dirstate._get_entry(0, 'foo-id'))
778
# extract the inventory, and add something to it
779
inv = tree._get_inventory()
780
# should see the file we poked in...
781
self.assertTrue(inv.has_id('foo-id'))
782
self.assertTrue(inv.has_filename('foo'))
783
inv.add_path('bar', 'file', 'bar-id')
784
tree._dirstate._validate()
785
# this used to cause it to lose its hashcache
786
tree._dirstate.set_state_from_inventory(inv)
787
tree._dirstate._validate()
793
# now check that the state still has the original hashcache value
794
state = tree._dirstate
796
foo_tuple = state._get_entry(0, path_utf8='foo')
798
(('', 'foo', 'foo-id',),
799
[('f', foo_sha, len(foo_contents), False,
800
dirstate.pack_stat(foo_stat))]),
806
def test_set_state_from_inventory_mixed_paths(self):
807
tree1 = self.make_branch_and_tree('tree1')
808
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
809
'tree1/a/b/foo', 'tree1/a-b/bar'])
812
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
813
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
814
tree1.commit('rev1', rev_id='rev1')
815
root_id = tree1.get_root_id()
816
inv = tree1.inventory
819
expected_result1 = [('', '', root_id, 'd'),
820
('', 'a', 'a-id', 'd'),
821
('', 'a-b', 'a-b-id', 'd'),
822
('a', 'b', 'b-id', 'd'),
823
('a/b', 'foo', 'foo-id', 'f'),
824
('a-b', 'bar', 'bar-id', 'f'),
826
expected_result2 = [('', '', root_id, 'd'),
827
('', 'a', 'a-id', 'd'),
828
('', 'a-b', 'a-b-id', 'd'),
829
('a-b', 'bar', 'bar-id', 'f'),
831
state = dirstate.DirState.initialize('dirstate')
833
state.set_state_from_inventory(inv)
835
for entry in state._iter_entries():
836
values.append(entry[0] + entry[1][0][:1])
837
self.assertEqual(expected_result1, values)
839
state.set_state_from_inventory(inv)
841
for entry in state._iter_entries():
842
values.append(entry[0] + entry[1][0][:1])
843
self.assertEqual(expected_result2, values)
847
def test_set_path_id_no_parents(self):
848
"""The id of a path can be changed trivally with no parents."""
849
state = dirstate.DirState.initialize('dirstate')
851
# check precondition to be sure the state does change appropriately.
853
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
854
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
855
list(state._iter_entries()))
856
state.set_path_id('', 'foobarbaz')
858
(('', '', 'foobarbaz'), [('d', '', 0, False,
859
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
860
self.assertEqual(expected_rows, list(state._iter_entries()))
861
# should work across save too
865
state = dirstate.DirState.on_file('dirstate')
869
self.assertEqual(expected_rows, list(state._iter_entries()))
873
def test_set_path_id_with_parents(self):
874
"""Set the root file id in a dirstate with parents"""
875
mt = self.make_branch_and_tree('mt')
876
# in case the default tree format uses a different root id
877
mt.set_root_id('TREE_ROOT')
878
mt.commit('foo', rev_id='parent-revid')
879
rt = mt.branch.repository.revision_tree('parent-revid')
880
state = dirstate.DirState.initialize('dirstate')
883
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
884
state.set_path_id('', 'foobarbaz')
886
# now see that it is what we expected
888
(('', '', 'TREE_ROOT'),
889
[('a', '', 0, False, ''),
890
('d', '', 0, False, 'parent-revid'),
892
(('', '', 'foobarbaz'),
893
[('d', '', 0, False, ''),
894
('a', '', 0, False, ''),
898
self.assertEqual(expected_rows, list(state._iter_entries()))
899
# should work across save too
903
# now flush & check we get the same
904
state = dirstate.DirState.on_file('dirstate')
908
self.assertEqual(expected_rows, list(state._iter_entries()))
911
# now change within an existing file-backed state
915
state.set_path_id('', 'tree-root-2')
921
def test_set_parent_trees_no_content(self):
922
# set_parent_trees is a slow but important api to support.
923
tree1 = self.make_branch_and_memory_tree('tree1')
927
revid1 = tree1.commit('foo')
930
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
931
tree2 = MemoryTree.create_on_branch(branch2)
934
revid2 = tree2.commit('foo')
935
root_id = tree2.get_root_id()
938
state = dirstate.DirState.initialize('dirstate')
940
state.set_path_id('', root_id)
941
state.set_parent_trees(
942
((revid1, tree1.branch.repository.revision_tree(revid1)),
943
(revid2, tree2.branch.repository.revision_tree(revid2)),
944
('ghost-rev', None)),
946
# check we can reopen and use the dirstate after setting parent
953
state = dirstate.DirState.on_file('dirstate')
956
self.assertEqual([revid1, revid2, 'ghost-rev'],
957
state.get_parent_ids())
958
# iterating the entire state ensures that the state is parsable.
959
list(state._iter_entries())
960
# be sure that it sets not appends - change it
961
state.set_parent_trees(
962
((revid1, tree1.branch.repository.revision_tree(revid1)),
963
('ghost-rev', None)),
965
# and now put it back.
966
state.set_parent_trees(
967
((revid1, tree1.branch.repository.revision_tree(revid1)),
968
(revid2, tree2.branch.repository.revision_tree(revid2)),
969
('ghost-rev', tree2.branch.repository.revision_tree(None))),
971
self.assertEqual([revid1, revid2, 'ghost-rev'],
972
state.get_parent_ids())
973
# the ghost should be recorded as such by set_parent_trees.
974
self.assertEqual(['ghost-rev'], state.get_ghosts())
976
[(('', '', root_id), [
977
('d', '', 0, False, dirstate.DirState.NULLSTAT),
978
('d', '', 0, False, revid1),
979
('d', '', 0, False, revid2)
981
list(state._iter_entries()))
985
def test_set_parent_trees_file_missing_from_tree(self):
986
# Adding a parent tree may reference files not in the current state.
987
# they should get listed just once by id, even if they are in two
989
# set_parent_trees is a slow but important api to support.
990
tree1 = self.make_branch_and_memory_tree('tree1')
994
tree1.add(['a file'], ['file-id'], ['file'])
995
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
996
revid1 = tree1.commit('foo')
999
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1000
tree2 = MemoryTree.create_on_branch(branch2)
1003
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1004
revid2 = tree2.commit('foo')
1005
root_id = tree2.get_root_id()
1008
# check the layout in memory
1009
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1010
(('', '', root_id), [
1011
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1012
('d', '', 0, False, revid1.encode('utf8')),
1013
('d', '', 0, False, revid2.encode('utf8'))
1015
(('', 'a file', 'file-id'), [
1016
('a', '', 0, False, ''),
1017
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1018
revid1.encode('utf8')),
1019
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1020
revid2.encode('utf8'))
1023
state = dirstate.DirState.initialize('dirstate')
1025
state.set_path_id('', root_id)
1026
state.set_parent_trees(
1027
((revid1, tree1.branch.repository.revision_tree(revid1)),
1028
(revid2, tree2.branch.repository.revision_tree(revid2)),
1034
# check_state_with_reopen will unlock
1035
self.check_state_with_reopen(expected_result, state)
1037
### add a path via _set_data - so we dont need delta work, just
1038
# raw data in, and ensure that it comes out via get_lines happily.
1040
def test_add_path_to_root_no_parents_all_data(self):
1041
# The most trivial addition of a path is when there are no parents and
1042
# its in the root and all data about the file is supplied
1043
self.build_tree(['a file'])
1044
stat = os.lstat('a file')
1045
# the 1*20 is the sha1 pretend value.
1046
state = dirstate.DirState.initialize('dirstate')
1047
expected_entries = [
1048
(('', '', 'TREE_ROOT'), [
1049
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1051
(('', 'a file', 'a-file-id'), [
1052
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1056
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1057
# having added it, it should be in the output of iter_entries.
1058
self.assertEqual(expected_entries, list(state._iter_entries()))
1059
# saving and reloading should not affect this.
1063
state = dirstate.DirState.on_file('dirstate')
1066
self.assertEqual(expected_entries, list(state._iter_entries()))
1070
def test_add_path_to_unversioned_directory(self):
1071
"""Adding a path to an unversioned directory should error.
1073
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1074
once dirstate is stable and if it is merged with WorkingTree3, consider
1075
removing this copy of the test.
1077
self.build_tree(['unversioned/', 'unversioned/a file'])
1078
state = dirstate.DirState.initialize('dirstate')
1080
self.assertRaises(errors.NotVersionedError, state.add,
1081
'unversioned/a file', 'a-file-id', 'file', None, None)
1085
def test_add_directory_to_root_no_parents_all_data(self):
1086
# The most trivial addition of a dir is when there are no parents and
1087
# its in the root and all data about the file is supplied
1088
self.build_tree(['a dir/'])
1089
stat = os.lstat('a dir')
1090
expected_entries = [
1091
(('', '', 'TREE_ROOT'), [
1092
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1094
(('', 'a dir', 'a dir id'), [
1095
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1098
state = dirstate.DirState.initialize('dirstate')
1100
state.add('a dir', 'a dir id', 'directory', stat, None)
1101
# having added it, it should be in the output of iter_entries.
1102
self.assertEqual(expected_entries, list(state._iter_entries()))
1103
# saving and reloading should not affect this.
1107
state = dirstate.DirState.on_file('dirstate')
1111
self.assertEqual(expected_entries, list(state._iter_entries()))
1115
def test_add_symlink_to_root_no_parents_all_data(self):
1116
# The most trivial addition of a symlink when there are no parents and
1117
# its in the root and all data about the file is supplied
1118
# bzr doesn't support fake symlinks on windows, yet.
1119
self.requireFeature(SymlinkFeature)
1120
os.symlink('target', 'a link')
1121
stat = os.lstat('a link')
1122
expected_entries = [
1123
(('', '', 'TREE_ROOT'), [
1124
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1126
(('', 'a link', 'a link id'), [
1127
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1130
state = dirstate.DirState.initialize('dirstate')
1132
state.add('a link', 'a link id', 'symlink', stat, 'target')
1133
# having added it, it should be in the output of iter_entries.
1134
self.assertEqual(expected_entries, list(state._iter_entries()))
1135
# saving and reloading should not affect this.
1139
state = dirstate.DirState.on_file('dirstate')
1142
self.assertEqual(expected_entries, list(state._iter_entries()))
1146
def test_add_directory_and_child_no_parents_all_data(self):
1147
# after adding a directory, we should be able to add children to it.
1148
self.build_tree(['a dir/', 'a dir/a file'])
1149
dirstat = os.lstat('a dir')
1150
filestat = os.lstat('a dir/a file')
1151
expected_entries = [
1152
(('', '', 'TREE_ROOT'), [
1153
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1155
(('', 'a dir', 'a dir id'), [
1156
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1158
(('a dir', 'a file', 'a-file-id'), [
1159
('f', '1'*20, 25, False,
1160
dirstate.pack_stat(filestat)), # current tree details
1163
state = dirstate.DirState.initialize('dirstate')
1165
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1166
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1167
# added it, it should be in the output of iter_entries.
1168
self.assertEqual(expected_entries, list(state._iter_entries()))
1169
# saving and reloading should not affect this.
1173
state = dirstate.DirState.on_file('dirstate')
1176
self.assertEqual(expected_entries, list(state._iter_entries()))
1180
def test_add_tree_reference(self):
1181
# make a dirstate and add a tree reference
1182
state = dirstate.DirState.initialize('dirstate')
1184
('', 'subdir', 'subdir-id'),
1185
[('t', 'subtree-123123', 0, False,
1186
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1189
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1190
entry = state._get_entry(0, 'subdir-id', 'subdir')
1191
self.assertEqual(entry, expected_entry)
1196
# now check we can read it back
1200
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1201
self.assertEqual(entry, entry2)
1202
self.assertEqual(entry, expected_entry)
1203
# and lookup by id should work too
1204
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1205
self.assertEqual(entry, expected_entry)
1209
def test_add_forbidden_names(self):
1210
state = dirstate.DirState.initialize('dirstate')
1211
self.addCleanup(state.unlock)
1212
self.assertRaises(errors.BzrError,
1213
state.add, '.', 'ass-id', 'directory', None, None)
1214
self.assertRaises(errors.BzrError,
1215
state.add, '..', 'ass-id', 'directory', None, None)
1218
class TestGetLines(TestCaseWithDirState):
1220
def test_get_line_with_2_rows(self):
1221
state = self.create_dirstate_with_root_and_subdir()
1223
self.assertEqual(['#bazaar dirstate flat format 3\n',
1224
'crc32: 41262208\n',
1228
'\x00\x00a-root-value\x00'
1229
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1230
'\x00subdir\x00subdir-id\x00'
1231
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1232
], state.get_lines())
1236
def test_entry_to_line(self):
1237
state = self.create_dirstate_with_root()
1240
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1241
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1242
state._entry_to_line(state._dirblocks[0][1][0]))
1246
def test_entry_to_line_with_parent(self):
1247
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1248
root_entry = ('', '', 'a-root-value'), [
1249
('d', '', 0, False, packed_stat), # current tree details
1250
# first: a pointer to the current location
1251
('a', 'dirname/basename', 0, False, ''),
1253
state = dirstate.DirState.initialize('dirstate')
1256
'\x00\x00a-root-value\x00'
1257
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1258
'a\x00dirname/basename\x000\x00n\x00',
1259
state._entry_to_line(root_entry))
1263
def test_entry_to_line_with_two_parents_at_different_paths(self):
1264
# / in the tree, at / in one parent and /dirname/basename in the other.
1265
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1266
root_entry = ('', '', 'a-root-value'), [
1267
('d', '', 0, False, packed_stat), # current tree details
1268
('d', '', 0, False, 'rev_id'), # first parent details
1269
# second: a pointer to the current location
1270
('a', 'dirname/basename', 0, False, ''),
1272
state = dirstate.DirState.initialize('dirstate')
1275
'\x00\x00a-root-value\x00'
1276
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1277
'd\x00\x000\x00n\x00rev_id\x00'
1278
'a\x00dirname/basename\x000\x00n\x00',
1279
state._entry_to_line(root_entry))
1283
def test_iter_entries(self):
1284
# we should be able to iterate the dirstate entries from end to end
1285
# this is for get_lines to be easy to read.
1286
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1288
root_entries = [(('', '', 'a-root-value'), [
1289
('d', '', 0, False, packed_stat), # current tree details
1291
dirblocks.append(('', root_entries))
1292
# add two files in the root
1293
subdir_entry = ('', 'subdir', 'subdir-id'), [
1294
('d', '', 0, False, packed_stat), # current tree details
1296
afile_entry = ('', 'afile', 'afile-id'), [
1297
('f', 'sha1value', 34, False, packed_stat), # current tree details
1299
dirblocks.append(('', [subdir_entry, afile_entry]))
1301
file_entry2 = ('subdir', '2file', '2file-id'), [
1302
('f', 'sha1value', 23, False, packed_stat), # current tree details
1304
dirblocks.append(('subdir', [file_entry2]))
1305
state = dirstate.DirState.initialize('dirstate')
1307
state._set_data([], dirblocks)
1308
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1310
self.assertEqual(expected_entries, list(state._iter_entries()))
1315
class TestGetBlockRowIndex(TestCaseWithDirState):
1317
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1318
file_present, state, dirname, basename, tree_index):
1319
self.assertEqual((block_index, row_index, dir_present, file_present),
1320
state._get_block_entry_index(dirname, basename, tree_index))
1322
block = state._dirblocks[block_index]
1323
self.assertEqual(dirname, block[0])
1324
if dir_present and file_present:
1325
row = state._dirblocks[block_index][1][row_index]
1326
self.assertEqual(dirname, row[0][0])
1327
self.assertEqual(basename, row[0][1])
1329
def test_simple_structure(self):
1330
state = self.create_dirstate_with_root_and_subdir()
1331
self.addCleanup(state.unlock)
1332
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1333
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1334
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1335
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1336
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1339
def test_complex_structure_exists(self):
1340
state = self.create_complex_dirstate()
1341
self.addCleanup(state.unlock)
1342
# Make sure we can find everything that exists
1343
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1344
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1345
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1346
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1347
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1348
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1349
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1350
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1351
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1352
'b', 'h\xc3\xa5', 0)
1354
def test_complex_structure_missing(self):
1355
state = self.create_complex_dirstate()
1356
self.addCleanup(state.unlock)
1357
# Make sure things would be inserted in the right locations
1358
# '_' comes before 'a'
1359
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1360
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1361
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1362
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1364
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1365
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1366
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1367
# This would be inserted between a/ and b/
1368
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1370
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1373
class TestGetEntry(TestCaseWithDirState):
1375
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1376
"""Check that the right entry is returned for a request to getEntry."""
1377
entry = state._get_entry(index, path_utf8=path)
1379
self.assertEqual((None, None), entry)
1382
self.assertEqual((dirname, basename, file_id), cur[:3])
1384
def test_simple_structure(self):
1385
state = self.create_dirstate_with_root_and_subdir()
1386
self.addCleanup(state.unlock)
1387
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1388
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1389
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1390
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1391
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1393
def test_complex_structure_exists(self):
1394
state = self.create_complex_dirstate()
1395
self.addCleanup(state.unlock)
1396
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1397
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1398
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1399
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1400
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1401
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1402
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1403
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1404
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1407
def test_complex_structure_missing(self):
1408
state = self.create_complex_dirstate()
1409
self.addCleanup(state.unlock)
1410
self.assertEntryEqual(None, None, None, state, '_', 0)
1411
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1412
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1413
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1415
def test_get_entry_uninitialized(self):
1416
"""Calling get_entry will load data if it needs to"""
1417
state = self.create_dirstate_with_root()
1423
state = dirstate.DirState.on_file('dirstate')
1426
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1427
state._header_state)
1428
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1429
state._dirblock_state)
1430
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1435
class TestIterChildEntries(TestCaseWithDirState):
1437
def create_dirstate_with_two_trees(self):
1438
"""This dirstate contains multiple files and directories.
1448
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1450
Notice that a/e is an empty directory.
1452
There is one parent tree, which has the same shape with the following variations:
1453
b/g in the parent is gone.
1454
b/h in the parent has a different id
1455
b/i is new in the parent
1456
c is renamed to b/j in the parent
1458
:return: The dirstate, still write-locked.
1460
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1461
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1462
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1463
root_entry = ('', '', 'a-root-value'), [
1464
('d', '', 0, False, packed_stat),
1465
('d', '', 0, False, 'parent-revid'),
1467
a_entry = ('', 'a', 'a-dir'), [
1468
('d', '', 0, False, packed_stat),
1469
('d', '', 0, False, 'parent-revid'),
1471
b_entry = ('', 'b', 'b-dir'), [
1472
('d', '', 0, False, packed_stat),
1473
('d', '', 0, False, 'parent-revid'),
1475
c_entry = ('', 'c', 'c-file'), [
1476
('f', null_sha, 10, False, packed_stat),
1477
('r', 'b/j', 0, False, ''),
1479
d_entry = ('', 'd', 'd-file'), [
1480
('f', null_sha, 20, False, packed_stat),
1481
('f', 'd', 20, False, 'parent-revid'),
1483
e_entry = ('a', 'e', 'e-dir'), [
1484
('d', '', 0, False, packed_stat),
1485
('d', '', 0, False, 'parent-revid'),
1487
f_entry = ('a', 'f', 'f-file'), [
1488
('f', null_sha, 30, False, packed_stat),
1489
('f', 'f', 20, False, 'parent-revid'),
1491
g_entry = ('b', 'g', 'g-file'), [
1492
('f', null_sha, 30, False, packed_stat),
1493
NULL_PARENT_DETAILS,
1495
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1496
('f', null_sha, 40, False, packed_stat),
1497
NULL_PARENT_DETAILS,
1499
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1500
NULL_PARENT_DETAILS,
1501
('f', 'h', 20, False, 'parent-revid'),
1503
i_entry = ('b', 'i', 'i-file'), [
1504
NULL_PARENT_DETAILS,
1505
('f', 'h', 20, False, 'parent-revid'),
1507
j_entry = ('b', 'j', 'c-file'), [
1508
('r', 'c', 0, False, ''),
1509
('f', 'j', 20, False, 'parent-revid'),
1512
dirblocks.append(('', [root_entry]))
1513
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1514
dirblocks.append(('a', [e_entry, f_entry]))
1515
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1516
state = dirstate.DirState.initialize('dirstate')
1519
state._set_data(['parent'], dirblocks)
1523
return state, dirblocks
1525
def test_iter_children_b(self):
1526
state, dirblocks = self.create_dirstate_with_two_trees()
1527
self.addCleanup(state.unlock)
1528
expected_result = []
1529
expected_result.append(dirblocks[3][1][2]) # h2
1530
expected_result.append(dirblocks[3][1][3]) # i
1531
expected_result.append(dirblocks[3][1][4]) # j
1532
self.assertEqual(expected_result,
1533
list(state._iter_child_entries(1, 'b')))
1535
def test_iter_child_root(self):
1536
state, dirblocks = self.create_dirstate_with_two_trees()
1537
self.addCleanup(state.unlock)
1538
expected_result = []
1539
expected_result.append(dirblocks[1][1][0]) # a
1540
expected_result.append(dirblocks[1][1][1]) # b
1541
expected_result.append(dirblocks[1][1][3]) # d
1542
expected_result.append(dirblocks[2][1][0]) # e
1543
expected_result.append(dirblocks[2][1][1]) # f
1544
expected_result.append(dirblocks[3][1][2]) # h2
1545
expected_result.append(dirblocks[3][1][3]) # i
1546
expected_result.append(dirblocks[3][1][4]) # j
1547
self.assertEqual(expected_result,
1548
list(state._iter_child_entries(1, '')))
1551
class TestDirstateSortOrder(TestCaseWithTransport):
1552
"""Test that DirState adds entries in the right order."""
1554
def test_add_sorting(self):
1555
"""Add entries in lexicographical order, we get path sorted order.
1557
This tests it to a depth of 4, to make sure we don't just get it right
1558
at a single depth. 'a/a' should come before 'a-a', even though it
1559
doesn't lexicographically.
1561
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1562
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1565
state = dirstate.DirState.initialize('dirstate')
1566
self.addCleanup(state.unlock)
1568
fake_stat = os.stat('dirstate')
1570
d_id = d.replace('/', '_')+'-id'
1571
file_path = d + '/f'
1572
file_id = file_path.replace('/', '_')+'-id'
1573
state.add(d, d_id, 'directory', fake_stat, null_sha)
1574
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1576
expected = ['', '', 'a',
1577
'a/a', 'a/a/a', 'a/a/a/a',
1578
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1580
split = lambda p:p.split('/')
1581
self.assertEqual(sorted(expected, key=split), expected)
1582
dirblock_names = [d[0] for d in state._dirblocks]
1583
self.assertEqual(expected, dirblock_names)
1585
def test_set_parent_trees_correct_order(self):
1586
"""After calling set_parent_trees() we should maintain the order."""
1587
dirs = ['a', 'a-a', 'a/a']
1589
state = dirstate.DirState.initialize('dirstate')
1590
self.addCleanup(state.unlock)
1592
fake_stat = os.stat('dirstate')
1594
d_id = d.replace('/', '_')+'-id'
1595
file_path = d + '/f'
1596
file_id = file_path.replace('/', '_')+'-id'
1597
state.add(d, d_id, 'directory', fake_stat, null_sha)
1598
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1600
expected = ['', '', 'a', 'a/a', 'a-a']
1601
dirblock_names = [d[0] for d in state._dirblocks]
1602
self.assertEqual(expected, dirblock_names)
1604
# *really* cheesy way to just get an empty tree
1605
repo = self.make_repository('repo')
1606
empty_tree = repo.revision_tree(None)
1607
state.set_parent_trees([('null:', empty_tree)], [])
1609
dirblock_names = [d[0] for d in state._dirblocks]
1610
self.assertEqual(expected, dirblock_names)
1613
class InstrumentedDirState(dirstate.DirState):
1614
"""An DirState with instrumented sha1 functionality."""
1616
def __init__(self, path):
1617
super(InstrumentedDirState, self).__init__(path)
1618
self._time_offset = 0
1620
# member is dynamically set in DirState.__init__ to turn on trace
1621
self._sha1_file = self._sha1_file_and_log
1623
def _sha_cutoff_time(self):
1624
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1625
self._cutoff_time = timestamp + self._time_offset
1627
def _sha1_file_and_log(self, abspath):
1628
self._log.append(('sha1', abspath))
1629
return osutils.sha_file_by_name(abspath)
1631
def _read_link(self, abspath, old_link):
1632
self._log.append(('read_link', abspath, old_link))
1633
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1635
def _lstat(self, abspath, entry):
1636
self._log.append(('lstat', abspath))
1637
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1639
def _is_executable(self, mode, old_executable):
1640
self._log.append(('is_exec', mode, old_executable))
1641
return super(InstrumentedDirState, self)._is_executable(mode,
1644
def adjust_time(self, secs):
1645
"""Move the clock forward or back.
1647
:param secs: The amount to adjust the clock by. Positive values make it
1648
seem as if we are in the future, negative values make it seem like we
1651
self._time_offset += secs
1652
self._cutoff_time = None
1655
class _FakeStat(object):
1656
"""A class with the same attributes as a real stat result."""
1658
def __init__(self, size, mtime, ctime, dev, ino, mode):
1660
self.st_mtime = mtime
1661
self.st_ctime = ctime
1667
class TestUpdateEntry(TestCaseWithDirState):
1668
"""Test the DirState.update_entry functions"""
1670
def get_state_with_a(self):
1671
"""Create a DirState tracking a single object named 'a'"""
1672
state = InstrumentedDirState.initialize('dirstate')
1673
self.addCleanup(state.unlock)
1674
state.add('a', 'a-id', 'file', None, '')
1675
entry = state._get_entry(0, path_utf8='a')
1678
def test_update_entry(self):
1679
state, entry = self.get_state_with_a()
1680
self.build_tree(['a'])
1681
# Add one where we don't provide the stat or sha already
1682
self.assertEqual(('', 'a', 'a-id'), entry[0])
1683
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1685
# Flush the buffers to disk
1687
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1688
state._dirblock_state)
1690
stat_value = os.lstat('a')
1691
packed_stat = dirstate.pack_stat(stat_value)
1692
link_or_sha1 = state.update_entry(entry, abspath='a',
1693
stat_value=stat_value)
1694
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1697
# The dirblock entry should not cache the file's sha1
1698
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1700
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1701
state._dirblock_state)
1702
mode = stat_value.st_mode
1703
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1706
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1707
state._dirblock_state)
1709
# If we do it again right away, we don't know if the file has changed
1710
# so we will re-read the file. Roll the clock back so the file is
1711
# guaranteed to look too new.
1712
state.adjust_time(-10)
1714
link_or_sha1 = state.update_entry(entry, abspath='a',
1715
stat_value=stat_value)
1716
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1717
('sha1', 'a'), ('is_exec', mode, False),
1719
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1721
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1722
state._dirblock_state)
1723
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1727
# However, if we move the clock forward so the file is considered
1728
# "stable", it should just cache the value.
1729
state.adjust_time(+20)
1730
link_or_sha1 = state.update_entry(entry, abspath='a',
1731
stat_value=stat_value)
1732
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1734
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1735
('sha1', 'a'), ('is_exec', mode, False),
1736
('sha1', 'a'), ('is_exec', mode, False),
1738
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1741
# Subsequent calls will just return the cached value
1742
link_or_sha1 = state.update_entry(entry, abspath='a',
1743
stat_value=stat_value)
1744
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1746
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1747
('sha1', 'a'), ('is_exec', mode, False),
1748
('sha1', 'a'), ('is_exec', mode, False),
1750
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1753
def test_update_entry_symlink(self):
1754
"""Update entry should read symlinks."""
1755
self.requireFeature(SymlinkFeature)
1756
state, entry = self.get_state_with_a()
1758
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1759
state._dirblock_state)
1760
os.symlink('target', 'a')
1762
state.adjust_time(-10) # Make the symlink look new
1763
stat_value = os.lstat('a')
1764
packed_stat = dirstate.pack_stat(stat_value)
1765
link_or_sha1 = state.update_entry(entry, abspath='a',
1766
stat_value=stat_value)
1767
self.assertEqual('target', link_or_sha1)
1768
self.assertEqual([('read_link', 'a', '')], state._log)
1769
# Dirblock is not updated (the link is too new)
1770
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1772
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1773
state._dirblock_state)
1775
# Because the stat_value looks new, we should re-read the target
1776
link_or_sha1 = state.update_entry(entry, abspath='a',
1777
stat_value=stat_value)
1778
self.assertEqual('target', link_or_sha1)
1779
self.assertEqual([('read_link', 'a', ''),
1780
('read_link', 'a', ''),
1782
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1784
state.adjust_time(+20) # Skip into the future, all files look old
1785
link_or_sha1 = state.update_entry(entry, abspath='a',
1786
stat_value=stat_value)
1787
self.assertEqual('target', link_or_sha1)
1788
# We need to re-read the link because only now can we cache it
1789
self.assertEqual([('read_link', 'a', ''),
1790
('read_link', 'a', ''),
1791
('read_link', 'a', ''),
1793
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1796
# Another call won't re-read the link
1797
self.assertEqual([('read_link', 'a', ''),
1798
('read_link', 'a', ''),
1799
('read_link', 'a', ''),
1801
link_or_sha1 = state.update_entry(entry, abspath='a',
1802
stat_value=stat_value)
1803
self.assertEqual('target', link_or_sha1)
1804
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1807
def do_update_entry(self, state, entry, abspath):
1808
stat_value = os.lstat(abspath)
1809
return state.update_entry(entry, abspath, stat_value)
1811
def test_update_entry_dir(self):
1812
state, entry = self.get_state_with_a()
1813
self.build_tree(['a/'])
1814
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1816
def test_update_entry_dir_unchanged(self):
1817
state, entry = self.get_state_with_a()
1818
self.build_tree(['a/'])
1819
state.adjust_time(+20)
1820
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1821
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1822
state._dirblock_state)
1824
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1825
state._dirblock_state)
1826
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1827
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1828
state._dirblock_state)
1830
def test_update_entry_file_unchanged(self):
1831
state, entry = self.get_state_with_a()
1832
self.build_tree(['a'])
1833
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1834
state.adjust_time(+20)
1835
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1836
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1837
state._dirblock_state)
1839
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1840
state._dirblock_state)
1841
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1842
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1843
state._dirblock_state)
1845
def create_and_test_file(self, state, entry):
1846
"""Create a file at 'a' and verify the state finds it.
1848
The state should already be versioning *something* at 'a'. This makes
1849
sure that state.update_entry recognizes it as a file.
1851
self.build_tree(['a'])
1852
stat_value = os.lstat('a')
1853
packed_stat = dirstate.pack_stat(stat_value)
1855
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1856
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1858
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1862
def create_and_test_dir(self, state, entry):
1863
"""Create a directory at 'a' and verify the state finds it.
1865
The state should already be versioning *something* at 'a'. This makes
1866
sure that state.update_entry recognizes it as a directory.
1868
self.build_tree(['a/'])
1869
stat_value = os.lstat('a')
1870
packed_stat = dirstate.pack_stat(stat_value)
1872
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1873
self.assertIs(None, link_or_sha1)
1874
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1878
def create_and_test_symlink(self, state, entry):
1879
"""Create a symlink at 'a' and verify the state finds it.
1881
The state should already be versioning *something* at 'a'. This makes
1882
sure that state.update_entry recognizes it as a symlink.
1884
This should not be called if this platform does not have symlink
1887
# caller should care about skipping test on platforms without symlinks
1888
os.symlink('path/to/foo', 'a')
1890
stat_value = os.lstat('a')
1891
packed_stat = dirstate.pack_stat(stat_value)
1893
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1894
self.assertEqual('path/to/foo', link_or_sha1)
1895
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1899
def test_update_file_to_dir(self):
1900
"""If a file changes to a directory we return None for the sha.
1901
We also update the inventory record.
1903
state, entry = self.get_state_with_a()
1904
# The file sha1 won't be cached unless the file is old
1905
state.adjust_time(+10)
1906
self.create_and_test_file(state, entry)
1908
self.create_and_test_dir(state, entry)
1910
def test_update_file_to_symlink(self):
1911
"""File becomes a symlink"""
1912
self.requireFeature(SymlinkFeature)
1913
state, entry = self.get_state_with_a()
1914
# The file sha1 won't be cached unless the file is old
1915
state.adjust_time(+10)
1916
self.create_and_test_file(state, entry)
1918
self.create_and_test_symlink(state, entry)
1920
def test_update_dir_to_file(self):
1921
"""Directory becoming a file updates the entry."""
1922
state, entry = self.get_state_with_a()
1923
# The file sha1 won't be cached unless the file is old
1924
state.adjust_time(+10)
1925
self.create_and_test_dir(state, entry)
1927
self.create_and_test_file(state, entry)
1929
def test_update_dir_to_symlink(self):
1930
"""Directory becomes a symlink"""
1931
self.requireFeature(SymlinkFeature)
1932
state, entry = self.get_state_with_a()
1933
# The symlink target won't be cached if it isn't old
1934
state.adjust_time(+10)
1935
self.create_and_test_dir(state, entry)
1937
self.create_and_test_symlink(state, entry)
1939
def test_update_symlink_to_file(self):
1940
"""Symlink becomes a file"""
1941
self.requireFeature(SymlinkFeature)
1942
state, entry = self.get_state_with_a()
1943
# The symlink and file info won't be cached unless old
1944
state.adjust_time(+10)
1945
self.create_and_test_symlink(state, entry)
1947
self.create_and_test_file(state, entry)
1949
def test_update_symlink_to_dir(self):
1950
"""Symlink becomes a directory"""
1951
self.requireFeature(SymlinkFeature)
1952
state, entry = self.get_state_with_a()
1953
# The symlink target won't be cached if it isn't old
1954
state.adjust_time(+10)
1955
self.create_and_test_symlink(state, entry)
1957
self.create_and_test_dir(state, entry)
1959
def test__is_executable_win32(self):
1960
state, entry = self.get_state_with_a()
1961
self.build_tree(['a'])
1963
# Make sure we are using the win32 implementation of _is_executable
1964
state._is_executable = state._is_executable_win32
1966
# The file on disk is not executable, but we are marking it as though
1967
# it is. With _is_executable_win32 we ignore what is on disk.
1968
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1970
stat_value = os.lstat('a')
1971
packed_stat = dirstate.pack_stat(stat_value)
1973
state.adjust_time(-10) # Make sure everything is new
1974
state.update_entry(entry, abspath='a', stat_value=stat_value)
1976
# The row is updated, but the executable bit stays set.
1977
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1980
# Make the disk object look old enough to cache
1981
state.adjust_time(+20)
1982
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1983
state.update_entry(entry, abspath='a', stat_value=stat_value)
1984
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1987
class TestPackStat(TestCaseWithTransport):
1989
def assertPackStat(self, expected, stat_value):
1990
"""Check the packed and serialized form of a stat value."""
1991
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1993
def test_pack_stat_int(self):
1994
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1995
# Make sure that all parameters have an impact on the packed stat.
1996
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1999
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
2000
st.st_mtime = 1172758620
2002
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
2003
st.st_ctime = 1172758630
2005
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2008
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
2009
st.st_ino = 6499540L
2011
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
2012
st.st_mode = 0100744
2014
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
2016
def test_pack_stat_float(self):
2017
"""On some platforms mtime and ctime are floats.
2019
Make sure we don't get warnings or errors, and that we ignore changes <
2022
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
2023
777L, 6499538L, 0100644)
2024
# These should all be the same as the integer counterparts
2025
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
2026
st.st_mtime = 1172758620.0
2028
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
2029
st.st_ctime = 1172758630.0
2031
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2032
# fractional seconds are discarded, so no change from above
2033
st.st_mtime = 1172758620.453
2034
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2035
st.st_ctime = 1172758630.228
2036
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2039
class TestBisect(TestCaseWithDirState):
2040
"""Test the ability to bisect into the disk format."""
2042
def assertBisect(self, expected_map, map_keys, state, paths):
2043
"""Assert that bisecting for paths returns the right result.
2045
:param expected_map: A map from key => entry value
2046
:param map_keys: The keys to expect for each path
2047
:param state: The DirState object.
2048
:param paths: A list of paths, these will automatically be split into
2049
(dir, name) tuples, and sorted according to how _bisect
2052
result = state._bisect(paths)
2053
# For now, results are just returned in whatever order we read them.
2054
# We could sort by (dir, name, file_id) or something like that, but in
2055
# the end it would still be fairly arbitrary, and we don't want the
2056
# extra overhead if we can avoid it. So sort everything to make sure
2058
self.assertEqual(len(map_keys), len(paths))
2060
for path, keys in zip(paths, map_keys):
2062
# This should not be present in the output
2064
expected[path] = sorted(expected_map[k] for k in keys)
2066
# The returned values are just arranged randomly based on when they
2067
# were read, for testing, make sure it is properly sorted.
2071
self.assertEqual(expected, result)
2073
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
2074
"""Assert that bisecting for dirbblocks returns the right result.
2076
:param expected_map: A map from key => expected values
2077
:param map_keys: A nested list of paths we expect to be returned.
2078
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
2079
:param state: The DirState object.
2080
:param paths: A list of directories
2082
result = state._bisect_dirblocks(paths)
2083
self.assertEqual(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)
2520
class Test_InvEntryToDetails(TestCaseWithDirState):
2522
def assertDetails(self, expected, inv_entry):
2523
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2524
self.assertEqual(expected, details)
2525
# details should always allow join() and always be a plain str when
2527
(minikind, fingerprint, size, executable, tree_data) = details
2528
self.assertIsInstance(minikind, str)
2529
self.assertIsInstance(fingerprint, str)
2530
self.assertIsInstance(tree_data, str)
2532
def test_unicode_symlink(self):
2533
# In general, the code base doesn't support a target that contains
2534
# non-ascii characters. So we just assert tha
2535
inv_entry = inventory.InventoryLink('link-file-id', 'name',
2537
inv_entry.revision = 'link-revision-id'
2538
inv_entry.symlink_target = u'link-target'
2539
details = self.assertDetails(('l', 'link-target', 0, False,
2540
'link-revision-id'), inv_entry)