1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
28
revision as _mod_revision,
30
from bzrlib.memorytree import MemoryTree
31
from bzrlib.tests import (
34
TestCaseWithTransport,
40
# general checks for NOT_IN_MEMORY error conditions.
41
# set_path_id on a NOT_IN_MEMORY dirstate
42
# set_path_id unicode support
43
# set_path_id setting id of a path not root
44
# set_path_id setting id when there are parents without the id in the parents
45
# set_path_id setting id when there are parents with the id in the parents
46
# set_path_id setting id when state is not in memory
47
# set_path_id setting id when state is in memory unmodified
48
# set_path_id setting id when state is in memory modified
51
class TestCaseWithDirState(TestCaseWithTransport):
52
"""Helper functions for creating DirState objects with various content."""
54
def create_empty_dirstate(self):
55
"""Return a locked but empty dirstate"""
56
state = dirstate.DirState.initialize('dirstate')
59
def create_dirstate_with_root(self):
60
"""Return a write-locked state with a single root entry."""
61
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
62
root_entry_direntry = ('', '', 'a-root-value'), [
63
('d', '', 0, False, packed_stat),
66
dirblocks.append(('', [root_entry_direntry]))
67
dirblocks.append(('', []))
68
state = self.create_empty_dirstate()
70
state._set_data([], dirblocks)
77
def create_dirstate_with_root_and_subdir(self):
78
"""Return a locked DirState with a root and a subdir"""
79
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
80
subdir_entry = ('', 'subdir', 'subdir-id'), [
81
('d', '', 0, False, packed_stat),
83
state = self.create_dirstate_with_root()
85
dirblocks = list(state._dirblocks)
86
dirblocks[1][1].append(subdir_entry)
87
state._set_data([], dirblocks)
93
def create_complex_dirstate(self):
94
"""This dirstate contains multiple files and directories.
104
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
106
Notice that a/e is an empty directory.
108
:return: The dirstate, still write-locked.
110
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
111
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
112
root_entry = ('', '', 'a-root-value'), [
113
('d', '', 0, False, packed_stat),
115
a_entry = ('', 'a', 'a-dir'), [
116
('d', '', 0, False, packed_stat),
118
b_entry = ('', 'b', 'b-dir'), [
119
('d', '', 0, False, packed_stat),
121
c_entry = ('', 'c', 'c-file'), [
122
('f', null_sha, 10, False, packed_stat),
124
d_entry = ('', 'd', 'd-file'), [
125
('f', null_sha, 20, False, packed_stat),
127
e_entry = ('a', 'e', 'e-dir'), [
128
('d', '', 0, False, packed_stat),
130
f_entry = ('a', 'f', 'f-file'), [
131
('f', null_sha, 30, False, packed_stat),
133
g_entry = ('b', 'g', 'g-file'), [
134
('f', null_sha, 30, False, packed_stat),
136
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
137
('f', null_sha, 40, False, packed_stat),
140
dirblocks.append(('', [root_entry]))
141
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
142
dirblocks.append(('a', [e_entry, f_entry]))
143
dirblocks.append(('b', [g_entry, h_entry]))
144
state = dirstate.DirState.initialize('dirstate')
147
state._set_data([], dirblocks)
153
def check_state_with_reopen(self, expected_result, state):
154
"""Check that state has current state expected_result.
156
This will check the current state, open the file anew and check it
158
This function expects the current state to be locked for writing, and
159
will unlock it before re-opening.
160
This is required because we can't open a lock_read() while something
161
else has a lock_write().
162
write => mutually exclusive lock
165
# The state should already be write locked, since we just had to do
166
# some operation to get here.
167
self.assertTrue(state._lock_token is not None)
169
self.assertEqual(expected_result[0], state.get_parent_ids())
170
# there should be no ghosts in this tree.
171
self.assertEqual([], state.get_ghosts())
172
# there should be one fileid in this tree - the root of the tree.
173
self.assertEqual(expected_result[1], list(state._iter_entries()))
178
state = dirstate.DirState.on_file('dirstate')
181
self.assertEqual(expected_result[1], list(state._iter_entries()))
185
def create_basic_dirstate(self):
186
"""Create a dirstate with a few files and directories.
196
tree = self.make_branch_and_tree('tree')
197
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
198
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
199
self.build_tree(['tree/' + p for p in paths])
200
tree.set_root_id('TREE_ROOT')
201
tree.add([p.rstrip('/') for p in paths], file_ids)
202
tree.commit('initial', rev_id='rev-1')
203
revision_id = 'rev-1'
204
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
205
t = self.get_transport('tree')
206
a_text = t.get_bytes('a')
207
a_sha = osutils.sha_string(a_text)
209
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
210
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
211
c_text = t.get_bytes('b/c')
212
c_sha = osutils.sha_string(c_text)
214
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
215
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
216
e_text = t.get_bytes('b/d/e')
217
e_sha = osutils.sha_string(e_text)
219
b_c_text = t.get_bytes('b-c')
220
b_c_sha = osutils.sha_string(b_c_text)
221
b_c_len = len(b_c_text)
222
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
223
f_text = t.get_bytes('f')
224
f_sha = osutils.sha_string(f_text)
226
null_stat = dirstate.DirState.NULLSTAT
228
'':(('', '', 'TREE_ROOT'), [
229
('d', '', 0, False, null_stat),
230
('d', '', 0, False, revision_id),
232
'a':(('', 'a', 'a-id'), [
233
('f', '', 0, False, null_stat),
234
('f', a_sha, a_len, False, revision_id),
236
'b':(('', 'b', 'b-id'), [
237
('d', '', 0, False, null_stat),
238
('d', '', 0, False, revision_id),
240
'b/c':(('b', 'c', 'c-id'), [
241
('f', '', 0, False, null_stat),
242
('f', c_sha, c_len, False, revision_id),
244
'b/d':(('b', 'd', 'd-id'), [
245
('d', '', 0, False, null_stat),
246
('d', '', 0, False, revision_id),
248
'b/d/e':(('b/d', 'e', 'e-id'), [
249
('f', '', 0, False, null_stat),
250
('f', e_sha, e_len, False, revision_id),
252
'b-c':(('', 'b-c', 'b-c-id'), [
253
('f', '', 0, False, null_stat),
254
('f', b_c_sha, b_c_len, False, revision_id),
256
'f':(('', 'f', 'f-id'), [
257
('f', '', 0, False, null_stat),
258
('f', f_sha, f_len, False, revision_id),
261
state = dirstate.DirState.from_tree(tree, 'dirstate')
266
# Use a different object, to make sure nothing is pre-cached in memory.
267
state = dirstate.DirState.on_file('dirstate')
269
self.addCleanup(state.unlock)
270
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
271
state._dirblock_state)
272
# This is code is only really tested if we actually have to make more
273
# than one read, so set the page size to something smaller.
274
# We want it to contain about 2.2 records, so that we have a couple
275
# records that we can read per attempt
276
state._bisect_page_size = 200
277
return tree, state, expected
279
def create_duplicated_dirstate(self):
280
"""Create a dirstate with a deleted and added entries.
282
This grabs a basic_dirstate, and then removes and re adds every entry
285
tree, state, expected = self.create_basic_dirstate()
286
# Now we will just remove and add every file so we get an extra entry
287
# per entry. Unversion in reverse order so we handle subdirs
288
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
289
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
290
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
292
# Update the expected dictionary.
293
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
294
orig = expected[path]
296
# This record was deleted in the current tree
297
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
299
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
300
# And didn't exist in the basis tree
301
expected[path2] = (new_key, [orig[1][0],
302
dirstate.DirState.NULL_PARENT_DETAILS])
304
# We will replace the 'dirstate' file underneath 'state', but that is
305
# okay as lock as we unlock 'state' first.
308
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
314
# But we need to leave state in a read-lock because we already have
315
# a cleanup scheduled
317
return tree, state, expected
319
def create_renamed_dirstate(self):
320
"""Create a dirstate with a few internal renames.
322
This takes the basic dirstate, and moves the paths around.
324
tree, state, expected = self.create_basic_dirstate()
326
tree.rename_one('a', 'b/g')
328
tree.rename_one('b/d', 'h')
330
old_a = expected['a']
331
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
332
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
333
('r', 'a', 0, False, '')])
334
old_d = expected['b/d']
335
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
336
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
337
('r', 'b/d', 0, False, '')])
339
old_e = expected['b/d/e']
340
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
342
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
343
('r', 'b/d/e', 0, False, '')])
347
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
354
return tree, state, expected
357
class TestTreeToDirState(TestCaseWithDirState):
359
def test_empty_to_dirstate(self):
360
"""We should be able to create a dirstate for an empty tree."""
361
# There are no files on disk and no parents
362
tree = self.make_branch_and_tree('tree')
363
expected_result = ([], [
364
(('', '', tree.get_root_id()), # common details
365
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
367
state = dirstate.DirState.from_tree(tree, 'dirstate')
369
self.check_state_with_reopen(expected_result, state)
371
def test_1_parents_empty_to_dirstate(self):
372
# create a parent by doing a commit
373
tree = self.make_branch_and_tree('tree')
374
rev_id = tree.commit('first post').encode('utf8')
375
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
376
expected_result = ([rev_id], [
377
(('', '', tree.get_root_id()), # common details
378
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
379
('d', '', 0, False, rev_id), # first parent details
381
state = dirstate.DirState.from_tree(tree, 'dirstate')
382
self.check_state_with_reopen(expected_result, state)
389
def test_2_parents_empty_to_dirstate(self):
390
# create a parent by doing a commit
391
tree = self.make_branch_and_tree('tree')
392
rev_id = tree.commit('first post')
393
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
394
rev_id2 = tree2.commit('second post', allow_pointless=True)
395
tree.merge_from_branch(tree2.branch)
396
expected_result = ([rev_id, rev_id2], [
397
(('', '', tree.get_root_id()), # common details
398
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
399
('d', '', 0, False, rev_id), # first parent details
400
('d', '', 0, False, rev_id2), # second parent details
402
state = dirstate.DirState.from_tree(tree, 'dirstate')
403
self.check_state_with_reopen(expected_result, state)
410
def test_empty_unknowns_are_ignored_to_dirstate(self):
411
"""We should be able to create a dirstate for an empty tree."""
412
# There are no files on disk and no parents
413
tree = self.make_branch_and_tree('tree')
414
self.build_tree(['tree/unknown'])
415
expected_result = ([], [
416
(('', '', tree.get_root_id()), # common details
417
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
419
state = dirstate.DirState.from_tree(tree, 'dirstate')
420
self.check_state_with_reopen(expected_result, state)
422
def get_tree_with_a_file(self):
423
tree = self.make_branch_and_tree('tree')
424
self.build_tree(['tree/a file'])
425
tree.add('a file', 'a-file-id')
428
def test_non_empty_no_parents_to_dirstate(self):
429
"""We should be able to create a dirstate for an empty tree."""
430
# There are files on disk and no parents
431
tree = self.get_tree_with_a_file()
432
expected_result = ([], [
433
(('', '', tree.get_root_id()), # common details
434
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
436
(('', 'a file', 'a-file-id'), # common
437
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
440
state = dirstate.DirState.from_tree(tree, 'dirstate')
441
self.check_state_with_reopen(expected_result, state)
443
def test_1_parents_not_empty_to_dirstate(self):
444
# create a parent by doing a commit
445
tree = self.get_tree_with_a_file()
446
rev_id = tree.commit('first post').encode('utf8')
447
# change the current content to be different this will alter stat, sha
449
self.build_tree_contents([('tree/a file', 'new content\n')])
450
expected_result = ([rev_id], [
451
(('', '', tree.get_root_id()), # common details
452
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
453
('d', '', 0, False, rev_id), # first parent details
455
(('', 'a file', 'a-file-id'), # common
456
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
457
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
458
rev_id), # first parent
461
state = dirstate.DirState.from_tree(tree, 'dirstate')
462
self.check_state_with_reopen(expected_result, state)
464
def test_2_parents_not_empty_to_dirstate(self):
465
# create a parent by doing a commit
466
tree = self.get_tree_with_a_file()
467
rev_id = tree.commit('first post').encode('utf8')
468
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
469
# change the current content to be different this will alter stat, sha
471
self.build_tree_contents([('tree2/a file', 'merge content\n')])
472
rev_id2 = tree2.commit('second post').encode('utf8')
473
tree.merge_from_branch(tree2.branch)
474
# change the current content to be different this will alter stat, sha
475
# and length again, giving us three distinct values:
476
self.build_tree_contents([('tree/a file', 'new content\n')])
477
expected_result = ([rev_id, rev_id2], [
478
(('', '', tree.get_root_id()), # common details
479
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
480
('d', '', 0, False, rev_id), # first parent details
481
('d', '', 0, False, rev_id2), # second parent details
483
(('', 'a file', 'a-file-id'), # common
484
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
485
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
486
rev_id), # first parent
487
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
488
rev_id2), # second parent
491
state = dirstate.DirState.from_tree(tree, 'dirstate')
492
self.check_state_with_reopen(expected_result, state)
494
def test_colliding_fileids(self):
495
# test insertion of parents creating several entries at the same path.
496
# we used to have a bug where they could cause the dirstate to break
497
# its ordering invariants.
498
# create some trees to test from
501
tree = self.make_branch_and_tree('tree%d' % i)
502
self.build_tree(['tree%d/name' % i,])
503
tree.add(['name'], ['file-id%d' % i])
504
revision_id = 'revid-%d' % i
505
tree.commit('message', rev_id=revision_id)
506
parents.append((revision_id,
507
tree.branch.repository.revision_tree(revision_id)))
508
# now fold these trees into a dirstate
509
state = dirstate.DirState.initialize('dirstate')
511
state.set_parent_trees(parents, [])
517
class TestDirStateOnFile(TestCaseWithDirState):
519
def test_construct_with_path(self):
520
tree = self.make_branch_and_tree('tree')
521
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
522
# we want to be able to get the lines of the dirstate that we will
524
lines = state.get_lines()
526
self.build_tree_contents([('dirstate', ''.join(lines))])
528
# no parents, default tree content
529
expected_result = ([], [
530
(('', '', tree.get_root_id()), # common details
531
# current tree details, but new from_tree skips statting, it
532
# uses set_state_from_inventory, and thus depends on the
534
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
537
state = dirstate.DirState.on_file('dirstate')
538
state.lock_write() # check_state_with_reopen will save() and unlock it
539
self.check_state_with_reopen(expected_result, state)
541
def test_can_save_clean_on_file(self):
542
tree = self.make_branch_and_tree('tree')
543
state = dirstate.DirState.from_tree(tree, 'dirstate')
545
# doing a save should work here as there have been no changes.
547
# TODO: stat it and check it hasn't changed; may require waiting
548
# for the state accuracy window.
552
def test_can_save_in_read_lock(self):
553
self.build_tree(['a-file'])
554
state = dirstate.DirState.initialize('dirstate')
556
# No stat and no sha1 sum.
557
state.add('a-file', 'a-file-id', 'file', None, '')
562
# Now open in readonly mode
563
state = dirstate.DirState.on_file('dirstate')
566
entry = state._get_entry(0, path_utf8='a-file')
567
# The current sha1 sum should be empty
568
self.assertEqual('', entry[1][0][1])
569
# We should have a real entry.
570
self.assertNotEqual((None, None), entry)
571
# Make sure everything is old enough
572
state._sha_cutoff_time()
573
state._cutoff_time += 10
574
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
575
# We should have gotten a real sha1
576
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
579
# The dirblock has been updated
580
self.assertEqual(sha1sum, entry[1][0][1])
581
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
582
state._dirblock_state)
585
# Now, since we are the only one holding a lock, we should be able
586
# to save and have it written to disk
591
# Re-open the file, and ensure that the state has been updated.
592
state = dirstate.DirState.on_file('dirstate')
595
entry = state._get_entry(0, path_utf8='a-file')
596
self.assertEqual(sha1sum, entry[1][0][1])
600
def test_save_fails_quietly_if_locked(self):
601
"""If dirstate is locked, save will fail without complaining."""
602
self.build_tree(['a-file'])
603
state = dirstate.DirState.initialize('dirstate')
605
# No stat and no sha1 sum.
606
state.add('a-file', 'a-file-id', 'file', None, '')
611
state = dirstate.DirState.on_file('dirstate')
614
entry = state._get_entry(0, path_utf8='a-file')
615
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
616
# We should have gotten a real sha1
617
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
619
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
620
state._dirblock_state)
622
# Now, before we try to save, grab another dirstate, and take out a
624
# TODO: jam 20070315 Ideally this would be locked by another
625
# process. To make sure the file is really OS locked.
626
state2 = dirstate.DirState.on_file('dirstate')
629
# This won't actually write anything, because it couldn't grab
630
# a write lock. But it shouldn't raise an error, either.
631
# TODO: jam 20070315 We should probably distinguish between
632
# being dirty because of 'update_entry'. And dirty
633
# because of real modification. So that save() *does*
634
# raise a real error if it fails when we have real
642
# The file on disk should not be modified.
643
state = dirstate.DirState.on_file('dirstate')
646
entry = state._get_entry(0, path_utf8='a-file')
647
self.assertEqual('', entry[1][0][1])
651
def test_save_refuses_if_changes_aborted(self):
652
self.build_tree(['a-file', 'a-dir/'])
653
state = dirstate.DirState.initialize('dirstate')
655
# No stat and no sha1 sum.
656
state.add('a-file', 'a-file-id', 'file', None, '')
661
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
663
('', [(('', '', 'TREE_ROOT'),
664
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
665
('', [(('', 'a-file', 'a-file-id'),
666
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
669
state = dirstate.DirState.on_file('dirstate')
672
state._read_dirblocks_if_needed()
673
self.assertEqual(expected_blocks, state._dirblocks)
675
# Now modify the state, but mark it as inconsistent
676
state.add('a-dir', 'a-dir-id', 'directory', None, '')
677
state._changes_aborted = True
682
state = dirstate.DirState.on_file('dirstate')
685
state._read_dirblocks_if_needed()
686
self.assertEqual(expected_blocks, state._dirblocks)
691
class TestDirStateInitialize(TestCaseWithDirState):
693
def test_initialize(self):
694
expected_result = ([], [
695
(('', '', 'TREE_ROOT'), # common details
696
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
699
state = dirstate.DirState.initialize('dirstate')
701
self.assertIsInstance(state, dirstate.DirState)
702
lines = state.get_lines()
705
# On win32 you can't read from a locked file, even within the same
706
# process. So we have to unlock and release before we check the file
708
self.assertFileEqual(''.join(lines), 'dirstate')
709
state.lock_read() # check_state_with_reopen will unlock
710
self.check_state_with_reopen(expected_result, state)
713
class TestDirStateManipulations(TestCaseWithDirState):
715
def test_set_state_from_inventory_no_content_no_parents(self):
716
# setting the current inventory is a slow but important api to support.
717
tree1 = self.make_branch_and_memory_tree('tree1')
721
revid1 = tree1.commit('foo').encode('utf8')
722
root_id = tree1.get_root_id()
723
inv = tree1.inventory
726
expected_result = [], [
727
(('', '', root_id), [
728
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
729
state = dirstate.DirState.initialize('dirstate')
731
state.set_state_from_inventory(inv)
732
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
734
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
735
state._dirblock_state)
740
# This will unlock it
741
self.check_state_with_reopen(expected_result, state)
743
def test_set_state_from_inventory_preserves_hashcache(self):
744
# https://bugs.launchpad.net/bzr/+bug/146176
745
# set_state_from_inventory should preserve the stat and hash value for
746
# workingtree files that are not changed by the inventory.
748
tree = self.make_branch_and_tree('.')
749
# depends on the default format using dirstate...
752
# make a dirstate with some valid hashcache data
753
# file on disk, but that's not needed for this test
754
foo_contents = 'contents of foo'
755
self.build_tree_contents([('foo', foo_contents)])
756
tree.add('foo', 'foo-id')
758
foo_stat = os.stat('foo')
759
foo_packed = dirstate.pack_stat(foo_stat)
760
foo_sha = osutils.sha_string(foo_contents)
761
foo_size = len(foo_contents)
763
# should not be cached yet, because the file's too fresh
765
(('', 'foo', 'foo-id',),
766
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
767
tree._dirstate._get_entry(0, 'foo-id'))
768
# poke in some hashcache information - it wouldn't normally be
769
# stored because it's too fresh
770
tree._dirstate.update_minimal(
771
('', 'foo', 'foo-id'),
772
'f', False, foo_sha, foo_packed, foo_size, 'foo')
773
# now should be cached
775
(('', 'foo', 'foo-id',),
776
[('f', foo_sha, foo_size, False, foo_packed)]),
777
tree._dirstate._get_entry(0, 'foo-id'))
779
# extract the inventory, and add something to it
780
inv = tree._get_inventory()
781
# should see the file we poked in...
782
self.assertTrue(inv.has_id('foo-id'))
783
self.assertTrue(inv.has_filename('foo'))
784
inv.add_path('bar', 'file', 'bar-id')
785
tree._dirstate._validate()
786
# this used to cause it to lose its hashcache
787
tree._dirstate.set_state_from_inventory(inv)
788
tree._dirstate._validate()
794
# now check that the state still has the original hashcache value
795
state = tree._dirstate
797
foo_tuple = state._get_entry(0, path_utf8='foo')
799
(('', 'foo', 'foo-id',),
800
[('f', foo_sha, len(foo_contents), False,
801
dirstate.pack_stat(foo_stat))]),
807
def test_set_state_from_inventory_mixed_paths(self):
808
tree1 = self.make_branch_and_tree('tree1')
809
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
810
'tree1/a/b/foo', 'tree1/a-b/bar'])
813
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
814
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
815
tree1.commit('rev1', rev_id='rev1')
816
root_id = tree1.get_root_id()
817
inv = tree1.inventory
820
expected_result1 = [('', '', root_id, 'd'),
821
('', 'a', 'a-id', 'd'),
822
('', 'a-b', 'a-b-id', 'd'),
823
('a', 'b', 'b-id', 'd'),
824
('a/b', 'foo', 'foo-id', 'f'),
825
('a-b', 'bar', 'bar-id', 'f'),
827
expected_result2 = [('', '', root_id, 'd'),
828
('', 'a', 'a-id', 'd'),
829
('', 'a-b', 'a-b-id', 'd'),
830
('a-b', 'bar', 'bar-id', 'f'),
832
state = dirstate.DirState.initialize('dirstate')
834
state.set_state_from_inventory(inv)
836
for entry in state._iter_entries():
837
values.append(entry[0] + entry[1][0][:1])
838
self.assertEqual(expected_result1, values)
840
state.set_state_from_inventory(inv)
842
for entry in state._iter_entries():
843
values.append(entry[0] + entry[1][0][:1])
844
self.assertEqual(expected_result2, values)
848
def test_set_path_id_no_parents(self):
849
"""The id of a path can be changed trivally with no parents."""
850
state = dirstate.DirState.initialize('dirstate')
852
# check precondition to be sure the state does change appropriately.
854
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
855
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
856
list(state._iter_entries()))
857
state.set_path_id('', 'foobarbaz')
859
(('', '', 'foobarbaz'), [('d', '', 0, False,
860
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
861
self.assertEqual(expected_rows, list(state._iter_entries()))
862
# should work across save too
866
state = dirstate.DirState.on_file('dirstate')
870
self.assertEqual(expected_rows, list(state._iter_entries()))
874
def test_set_path_id_with_parents(self):
875
"""Set the root file id in a dirstate with parents"""
876
mt = self.make_branch_and_tree('mt')
877
# in case the default tree format uses a different root id
878
mt.set_root_id('TREE_ROOT')
879
mt.commit('foo', rev_id='parent-revid')
880
rt = mt.branch.repository.revision_tree('parent-revid')
881
state = dirstate.DirState.initialize('dirstate')
884
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
885
state.set_path_id('', 'foobarbaz')
887
# now see that it is what we expected
889
(('', '', 'TREE_ROOT'),
890
[('a', '', 0, False, ''),
891
('d', '', 0, False, 'parent-revid'),
893
(('', '', 'foobarbaz'),
894
[('d', '', 0, False, ''),
895
('a', '', 0, False, ''),
899
self.assertEqual(expected_rows, list(state._iter_entries()))
900
# should work across save too
904
# now flush & check we get the same
905
state = dirstate.DirState.on_file('dirstate')
909
self.assertEqual(expected_rows, list(state._iter_entries()))
912
# now change within an existing file-backed state
916
state.set_path_id('', 'tree-root-2')
922
def test_set_parent_trees_no_content(self):
923
# set_parent_trees is a slow but important api to support.
924
tree1 = self.make_branch_and_memory_tree('tree1')
928
revid1 = tree1.commit('foo')
931
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
932
tree2 = MemoryTree.create_on_branch(branch2)
935
revid2 = tree2.commit('foo')
936
root_id = tree2.get_root_id()
939
state = dirstate.DirState.initialize('dirstate')
941
state.set_path_id('', root_id)
942
state.set_parent_trees(
943
((revid1, tree1.branch.repository.revision_tree(revid1)),
944
(revid2, tree2.branch.repository.revision_tree(revid2)),
945
('ghost-rev', None)),
947
# check we can reopen and use the dirstate after setting parent
954
state = dirstate.DirState.on_file('dirstate')
957
self.assertEqual([revid1, revid2, 'ghost-rev'],
958
state.get_parent_ids())
959
# iterating the entire state ensures that the state is parsable.
960
list(state._iter_entries())
961
# be sure that it sets not appends - change it
962
state.set_parent_trees(
963
((revid1, tree1.branch.repository.revision_tree(revid1)),
964
('ghost-rev', None)),
966
# and now put it back.
967
state.set_parent_trees(
968
((revid1, tree1.branch.repository.revision_tree(revid1)),
969
(revid2, tree2.branch.repository.revision_tree(revid2)),
970
('ghost-rev', tree2.branch.repository.revision_tree(
971
_mod_revision.NULL_REVISION))),
973
self.assertEqual([revid1, revid2, 'ghost-rev'],
974
state.get_parent_ids())
975
# the ghost should be recorded as such by set_parent_trees.
976
self.assertEqual(['ghost-rev'], state.get_ghosts())
978
[(('', '', root_id), [
979
('d', '', 0, False, dirstate.DirState.NULLSTAT),
980
('d', '', 0, False, revid1),
981
('d', '', 0, False, revid2)
983
list(state._iter_entries()))
987
def test_set_parent_trees_file_missing_from_tree(self):
988
# Adding a parent tree may reference files not in the current state.
989
# they should get listed just once by id, even if they are in two
991
# set_parent_trees is a slow but important api to support.
992
tree1 = self.make_branch_and_memory_tree('tree1')
996
tree1.add(['a file'], ['file-id'], ['file'])
997
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
998
revid1 = tree1.commit('foo')
1001
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1002
tree2 = MemoryTree.create_on_branch(branch2)
1005
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1006
revid2 = tree2.commit('foo')
1007
root_id = tree2.get_root_id()
1010
# check the layout in memory
1011
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1012
(('', '', root_id), [
1013
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1014
('d', '', 0, False, revid1.encode('utf8')),
1015
('d', '', 0, False, revid2.encode('utf8'))
1017
(('', 'a file', 'file-id'), [
1018
('a', '', 0, False, ''),
1019
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1020
revid1.encode('utf8')),
1021
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1022
revid2.encode('utf8'))
1025
state = dirstate.DirState.initialize('dirstate')
1027
state.set_path_id('', root_id)
1028
state.set_parent_trees(
1029
((revid1, tree1.branch.repository.revision_tree(revid1)),
1030
(revid2, tree2.branch.repository.revision_tree(revid2)),
1036
# check_state_with_reopen will unlock
1037
self.check_state_with_reopen(expected_result, state)
1039
### add a path via _set_data - so we dont need delta work, just
1040
# raw data in, and ensure that it comes out via get_lines happily.
1042
def test_add_path_to_root_no_parents_all_data(self):
1043
# The most trivial addition of a path is when there are no parents and
1044
# its in the root and all data about the file is supplied
1045
self.build_tree(['a file'])
1046
stat = os.lstat('a file')
1047
# the 1*20 is the sha1 pretend value.
1048
state = dirstate.DirState.initialize('dirstate')
1049
expected_entries = [
1050
(('', '', 'TREE_ROOT'), [
1051
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1053
(('', 'a file', 'a-file-id'), [
1054
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1058
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1059
# having added it, it should be in the output of iter_entries.
1060
self.assertEqual(expected_entries, list(state._iter_entries()))
1061
# saving and reloading should not affect this.
1065
state = dirstate.DirState.on_file('dirstate')
1068
self.assertEqual(expected_entries, list(state._iter_entries()))
1072
def test_add_path_to_unversioned_directory(self):
1073
"""Adding a path to an unversioned directory should error.
1075
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1076
once dirstate is stable and if it is merged with WorkingTree3, consider
1077
removing this copy of the test.
1079
self.build_tree(['unversioned/', 'unversioned/a file'])
1080
state = dirstate.DirState.initialize('dirstate')
1082
self.assertRaises(errors.NotVersionedError, state.add,
1083
'unversioned/a file', 'a-file-id', 'file', None, None)
1087
def test_add_directory_to_root_no_parents_all_data(self):
1088
# The most trivial addition of a dir is when there are no parents and
1089
# its in the root and all data about the file is supplied
1090
self.build_tree(['a dir/'])
1091
stat = os.lstat('a dir')
1092
expected_entries = [
1093
(('', '', 'TREE_ROOT'), [
1094
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1096
(('', 'a dir', 'a dir id'), [
1097
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1100
state = dirstate.DirState.initialize('dirstate')
1102
state.add('a dir', 'a dir id', 'directory', stat, None)
1103
# having added it, it should be in the output of iter_entries.
1104
self.assertEqual(expected_entries, list(state._iter_entries()))
1105
# saving and reloading should not affect this.
1109
state = dirstate.DirState.on_file('dirstate')
1113
self.assertEqual(expected_entries, list(state._iter_entries()))
1117
def test_add_symlink_to_root_no_parents_all_data(self):
1118
# The most trivial addition of a symlink when there are no parents and
1119
# its in the root and all data about the file is supplied
1120
# bzr doesn't support fake symlinks on windows, yet.
1121
self.requireFeature(SymlinkFeature)
1122
os.symlink('target', 'a link')
1123
stat = os.lstat('a link')
1124
expected_entries = [
1125
(('', '', 'TREE_ROOT'), [
1126
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1128
(('', 'a link', 'a link id'), [
1129
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1132
state = dirstate.DirState.initialize('dirstate')
1134
state.add('a link', 'a link id', 'symlink', stat, 'target')
1135
# having added it, it should be in the output of iter_entries.
1136
self.assertEqual(expected_entries, list(state._iter_entries()))
1137
# saving and reloading should not affect this.
1141
state = dirstate.DirState.on_file('dirstate')
1144
self.assertEqual(expected_entries, list(state._iter_entries()))
1148
def test_add_directory_and_child_no_parents_all_data(self):
1149
# after adding a directory, we should be able to add children to it.
1150
self.build_tree(['a dir/', 'a dir/a file'])
1151
dirstat = os.lstat('a dir')
1152
filestat = os.lstat('a dir/a file')
1153
expected_entries = [
1154
(('', '', 'TREE_ROOT'), [
1155
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1157
(('', 'a dir', 'a dir id'), [
1158
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1160
(('a dir', 'a file', 'a-file-id'), [
1161
('f', '1'*20, 25, False,
1162
dirstate.pack_stat(filestat)), # current tree details
1165
state = dirstate.DirState.initialize('dirstate')
1167
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1168
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1169
# added it, it should be in the output of iter_entries.
1170
self.assertEqual(expected_entries, list(state._iter_entries()))
1171
# saving and reloading should not affect this.
1175
state = dirstate.DirState.on_file('dirstate')
1178
self.assertEqual(expected_entries, list(state._iter_entries()))
1182
def test_add_tree_reference(self):
1183
# make a dirstate and add a tree reference
1184
state = dirstate.DirState.initialize('dirstate')
1186
('', 'subdir', 'subdir-id'),
1187
[('t', 'subtree-123123', 0, False,
1188
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1191
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1192
entry = state._get_entry(0, 'subdir-id', 'subdir')
1193
self.assertEqual(entry, expected_entry)
1198
# now check we can read it back
1202
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1203
self.assertEqual(entry, entry2)
1204
self.assertEqual(entry, expected_entry)
1205
# and lookup by id should work too
1206
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1207
self.assertEqual(entry, expected_entry)
1211
def test_add_forbidden_names(self):
1212
state = dirstate.DirState.initialize('dirstate')
1213
self.addCleanup(state.unlock)
1214
self.assertRaises(errors.BzrError,
1215
state.add, '.', 'ass-id', 'directory', None, None)
1216
self.assertRaises(errors.BzrError,
1217
state.add, '..', 'ass-id', 'directory', None, None)
1220
class TestGetLines(TestCaseWithDirState):
1222
def test_get_line_with_2_rows(self):
1223
state = self.create_dirstate_with_root_and_subdir()
1225
self.assertEqual(['#bazaar dirstate flat format 3\n',
1226
'crc32: 41262208\n',
1230
'\x00\x00a-root-value\x00'
1231
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1232
'\x00subdir\x00subdir-id\x00'
1233
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1234
], state.get_lines())
1238
def test_entry_to_line(self):
1239
state = self.create_dirstate_with_root()
1242
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1243
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1244
state._entry_to_line(state._dirblocks[0][1][0]))
1248
def test_entry_to_line_with_parent(self):
1249
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1250
root_entry = ('', '', 'a-root-value'), [
1251
('d', '', 0, False, packed_stat), # current tree details
1252
# first: a pointer to the current location
1253
('a', 'dirname/basename', 0, False, ''),
1255
state = dirstate.DirState.initialize('dirstate')
1258
'\x00\x00a-root-value\x00'
1259
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1260
'a\x00dirname/basename\x000\x00n\x00',
1261
state._entry_to_line(root_entry))
1265
def test_entry_to_line_with_two_parents_at_different_paths(self):
1266
# / in the tree, at / in one parent and /dirname/basename in the other.
1267
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1268
root_entry = ('', '', 'a-root-value'), [
1269
('d', '', 0, False, packed_stat), # current tree details
1270
('d', '', 0, False, 'rev_id'), # first parent details
1271
# second: a pointer to the current location
1272
('a', 'dirname/basename', 0, False, ''),
1274
state = dirstate.DirState.initialize('dirstate')
1277
'\x00\x00a-root-value\x00'
1278
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1279
'd\x00\x000\x00n\x00rev_id\x00'
1280
'a\x00dirname/basename\x000\x00n\x00',
1281
state._entry_to_line(root_entry))
1285
def test_iter_entries(self):
1286
# we should be able to iterate the dirstate entries from end to end
1287
# this is for get_lines to be easy to read.
1288
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1290
root_entries = [(('', '', 'a-root-value'), [
1291
('d', '', 0, False, packed_stat), # current tree details
1293
dirblocks.append(('', root_entries))
1294
# add two files in the root
1295
subdir_entry = ('', 'subdir', 'subdir-id'), [
1296
('d', '', 0, False, packed_stat), # current tree details
1298
afile_entry = ('', 'afile', 'afile-id'), [
1299
('f', 'sha1value', 34, False, packed_stat), # current tree details
1301
dirblocks.append(('', [subdir_entry, afile_entry]))
1303
file_entry2 = ('subdir', '2file', '2file-id'), [
1304
('f', 'sha1value', 23, False, packed_stat), # current tree details
1306
dirblocks.append(('subdir', [file_entry2]))
1307
state = dirstate.DirState.initialize('dirstate')
1309
state._set_data([], dirblocks)
1310
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1312
self.assertEqual(expected_entries, list(state._iter_entries()))
1317
class TestGetBlockRowIndex(TestCaseWithDirState):
1319
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1320
file_present, state, dirname, basename, tree_index):
1321
self.assertEqual((block_index, row_index, dir_present, file_present),
1322
state._get_block_entry_index(dirname, basename, tree_index))
1324
block = state._dirblocks[block_index]
1325
self.assertEqual(dirname, block[0])
1326
if dir_present and file_present:
1327
row = state._dirblocks[block_index][1][row_index]
1328
self.assertEqual(dirname, row[0][0])
1329
self.assertEqual(basename, row[0][1])
1331
def test_simple_structure(self):
1332
state = self.create_dirstate_with_root_and_subdir()
1333
self.addCleanup(state.unlock)
1334
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1335
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1336
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1337
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1338
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1341
def test_complex_structure_exists(self):
1342
state = self.create_complex_dirstate()
1343
self.addCleanup(state.unlock)
1344
# Make sure we can find everything that exists
1345
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1346
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1347
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1348
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1349
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1350
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1351
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1352
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1353
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1354
'b', 'h\xc3\xa5', 0)
1356
def test_complex_structure_missing(self):
1357
state = self.create_complex_dirstate()
1358
self.addCleanup(state.unlock)
1359
# Make sure things would be inserted in the right locations
1360
# '_' comes before 'a'
1361
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1362
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1363
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1364
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1366
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1367
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1368
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1369
# This would be inserted between a/ and b/
1370
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1372
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1375
class TestGetEntry(TestCaseWithDirState):
1377
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1378
"""Check that the right entry is returned for a request to getEntry."""
1379
entry = state._get_entry(index, path_utf8=path)
1381
self.assertEqual((None, None), entry)
1384
self.assertEqual((dirname, basename, file_id), cur[:3])
1386
def test_simple_structure(self):
1387
state = self.create_dirstate_with_root_and_subdir()
1388
self.addCleanup(state.unlock)
1389
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1390
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1391
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1392
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1393
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1395
def test_complex_structure_exists(self):
1396
state = self.create_complex_dirstate()
1397
self.addCleanup(state.unlock)
1398
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1399
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1400
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1401
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1402
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1403
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1404
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1405
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1406
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1409
def test_complex_structure_missing(self):
1410
state = self.create_complex_dirstate()
1411
self.addCleanup(state.unlock)
1412
self.assertEntryEqual(None, None, None, state, '_', 0)
1413
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1414
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1415
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1417
def test_get_entry_uninitialized(self):
1418
"""Calling get_entry will load data if it needs to"""
1419
state = self.create_dirstate_with_root()
1425
state = dirstate.DirState.on_file('dirstate')
1428
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1429
state._header_state)
1430
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1431
state._dirblock_state)
1432
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1437
class TestIterChildEntries(TestCaseWithDirState):
1439
def create_dirstate_with_two_trees(self):
1440
"""This dirstate contains multiple files and directories.
1450
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1452
Notice that a/e is an empty directory.
1454
There is one parent tree, which has the same shape with the following variations:
1455
b/g in the parent is gone.
1456
b/h in the parent has a different id
1457
b/i is new in the parent
1458
c is renamed to b/j in the parent
1460
:return: The dirstate, still write-locked.
1462
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1463
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1464
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1465
root_entry = ('', '', 'a-root-value'), [
1466
('d', '', 0, False, packed_stat),
1467
('d', '', 0, False, 'parent-revid'),
1469
a_entry = ('', 'a', 'a-dir'), [
1470
('d', '', 0, False, packed_stat),
1471
('d', '', 0, False, 'parent-revid'),
1473
b_entry = ('', 'b', 'b-dir'), [
1474
('d', '', 0, False, packed_stat),
1475
('d', '', 0, False, 'parent-revid'),
1477
c_entry = ('', 'c', 'c-file'), [
1478
('f', null_sha, 10, False, packed_stat),
1479
('r', 'b/j', 0, False, ''),
1481
d_entry = ('', 'd', 'd-file'), [
1482
('f', null_sha, 20, False, packed_stat),
1483
('f', 'd', 20, False, 'parent-revid'),
1485
e_entry = ('a', 'e', 'e-dir'), [
1486
('d', '', 0, False, packed_stat),
1487
('d', '', 0, False, 'parent-revid'),
1489
f_entry = ('a', 'f', 'f-file'), [
1490
('f', null_sha, 30, False, packed_stat),
1491
('f', 'f', 20, False, 'parent-revid'),
1493
g_entry = ('b', 'g', 'g-file'), [
1494
('f', null_sha, 30, False, packed_stat),
1495
NULL_PARENT_DETAILS,
1497
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1498
('f', null_sha, 40, False, packed_stat),
1499
NULL_PARENT_DETAILS,
1501
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1502
NULL_PARENT_DETAILS,
1503
('f', 'h', 20, False, 'parent-revid'),
1505
i_entry = ('b', 'i', 'i-file'), [
1506
NULL_PARENT_DETAILS,
1507
('f', 'h', 20, False, 'parent-revid'),
1509
j_entry = ('b', 'j', 'c-file'), [
1510
('r', 'c', 0, False, ''),
1511
('f', 'j', 20, False, 'parent-revid'),
1514
dirblocks.append(('', [root_entry]))
1515
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1516
dirblocks.append(('a', [e_entry, f_entry]))
1517
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1518
state = dirstate.DirState.initialize('dirstate')
1521
state._set_data(['parent'], dirblocks)
1525
return state, dirblocks
1527
def test_iter_children_b(self):
1528
state, dirblocks = self.create_dirstate_with_two_trees()
1529
self.addCleanup(state.unlock)
1530
expected_result = []
1531
expected_result.append(dirblocks[3][1][2]) # h2
1532
expected_result.append(dirblocks[3][1][3]) # i
1533
expected_result.append(dirblocks[3][1][4]) # j
1534
self.assertEqual(expected_result,
1535
list(state._iter_child_entries(1, 'b')))
1537
def test_iter_child_root(self):
1538
state, dirblocks = self.create_dirstate_with_two_trees()
1539
self.addCleanup(state.unlock)
1540
expected_result = []
1541
expected_result.append(dirblocks[1][1][0]) # a
1542
expected_result.append(dirblocks[1][1][1]) # b
1543
expected_result.append(dirblocks[1][1][3]) # d
1544
expected_result.append(dirblocks[2][1][0]) # e
1545
expected_result.append(dirblocks[2][1][1]) # f
1546
expected_result.append(dirblocks[3][1][2]) # h2
1547
expected_result.append(dirblocks[3][1][3]) # i
1548
expected_result.append(dirblocks[3][1][4]) # j
1549
self.assertEqual(expected_result,
1550
list(state._iter_child_entries(1, '')))
1553
class TestDirstateSortOrder(TestCaseWithTransport):
1554
"""Test that DirState adds entries in the right order."""
1556
def test_add_sorting(self):
1557
"""Add entries in lexicographical order, we get path sorted order.
1559
This tests it to a depth of 4, to make sure we don't just get it right
1560
at a single depth. 'a/a' should come before 'a-a', even though it
1561
doesn't lexicographically.
1563
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1564
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1567
state = dirstate.DirState.initialize('dirstate')
1568
self.addCleanup(state.unlock)
1570
fake_stat = os.stat('dirstate')
1572
d_id = d.replace('/', '_')+'-id'
1573
file_path = d + '/f'
1574
file_id = file_path.replace('/', '_')+'-id'
1575
state.add(d, d_id, 'directory', fake_stat, null_sha)
1576
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1578
expected = ['', '', 'a',
1579
'a/a', 'a/a/a', 'a/a/a/a',
1580
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1582
split = lambda p:p.split('/')
1583
self.assertEqual(sorted(expected, key=split), expected)
1584
dirblock_names = [d[0] for d in state._dirblocks]
1585
self.assertEqual(expected, dirblock_names)
1587
def test_set_parent_trees_correct_order(self):
1588
"""After calling set_parent_trees() we should maintain the order."""
1589
dirs = ['a', 'a-a', 'a/a']
1591
state = dirstate.DirState.initialize('dirstate')
1592
self.addCleanup(state.unlock)
1594
fake_stat = os.stat('dirstate')
1596
d_id = d.replace('/', '_')+'-id'
1597
file_path = d + '/f'
1598
file_id = file_path.replace('/', '_')+'-id'
1599
state.add(d, d_id, 'directory', fake_stat, null_sha)
1600
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1602
expected = ['', '', 'a', 'a/a', 'a-a']
1603
dirblock_names = [d[0] for d in state._dirblocks]
1604
self.assertEqual(expected, dirblock_names)
1606
# *really* cheesy way to just get an empty tree
1607
repo = self.make_repository('repo')
1608
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1609
state.set_parent_trees([('null:', empty_tree)], [])
1611
dirblock_names = [d[0] for d in state._dirblocks]
1612
self.assertEqual(expected, dirblock_names)
1615
class InstrumentedDirState(dirstate.DirState):
1616
"""An DirState with instrumented sha1 functionality."""
1618
def __init__(self, path):
1619
super(InstrumentedDirState, self).__init__(path)
1620
self._time_offset = 0
1622
# member is dynamically set in DirState.__init__ to turn on trace
1623
self._sha1_file = self._sha1_file_and_log
1625
def _sha_cutoff_time(self):
1626
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1627
self._cutoff_time = timestamp + self._time_offset
1629
def _sha1_file_and_log(self, abspath):
1630
self._log.append(('sha1', abspath))
1631
return osutils.sha_file_by_name(abspath)
1633
def _read_link(self, abspath, old_link):
1634
self._log.append(('read_link', abspath, old_link))
1635
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1637
def _lstat(self, abspath, entry):
1638
self._log.append(('lstat', abspath))
1639
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1641
def _is_executable(self, mode, old_executable):
1642
self._log.append(('is_exec', mode, old_executable))
1643
return super(InstrumentedDirState, self)._is_executable(mode,
1646
def adjust_time(self, secs):
1647
"""Move the clock forward or back.
1649
:param secs: The amount to adjust the clock by. Positive values make it
1650
seem as if we are in the future, negative values make it seem like we
1653
self._time_offset += secs
1654
self._cutoff_time = None
1657
class _FakeStat(object):
1658
"""A class with the same attributes as a real stat result."""
1660
def __init__(self, size, mtime, ctime, dev, ino, mode):
1662
self.st_mtime = mtime
1663
self.st_ctime = ctime
1669
class TestUpdateEntry(TestCaseWithDirState):
1670
"""Test the DirState.update_entry functions"""
1672
def get_state_with_a(self):
1673
"""Create a DirState tracking a single object named 'a'"""
1674
state = InstrumentedDirState.initialize('dirstate')
1675
self.addCleanup(state.unlock)
1676
state.add('a', 'a-id', 'file', None, '')
1677
entry = state._get_entry(0, path_utf8='a')
1680
def test_observed_sha1_cachable(self):
1681
state, entry = self.get_state_with_a()
1682
atime = time.time() - 10
1683
self.build_tree(['a'])
1684
statvalue = os.lstat('a')
1685
statvalue = _FakeStat(statvalue.st_size, atime, atime,
1686
statvalue.st_dev, statvalue.st_ino, statvalue.st_mode)
1687
state._observed_sha1(entry, "foo", statvalue)
1688
self.assertEqual('foo', entry[1][0][1])
1689
packed_stat = dirstate.pack_stat(statvalue)
1690
self.assertEqual(packed_stat, entry[1][0][4])
1692
def test_observed_sha1_not_cachable(self):
1693
state, entry = self.get_state_with_a()
1694
oldval = entry[1][0][1]
1695
oldstat = entry[1][0][4]
1696
self.build_tree(['a'])
1697
statvalue = os.lstat('a')
1698
state._observed_sha1(entry, "foo", statvalue)
1699
self.assertEqual(oldval, entry[1][0][1])
1700
self.assertEqual(oldstat, entry[1][0][4])
1702
def test_update_entry(self):
1703
state, entry = self.get_state_with_a()
1704
self.build_tree(['a'])
1705
# Add one where we don't provide the stat or sha already
1706
self.assertEqual(('', 'a', 'a-id'), entry[0])
1707
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1709
# Flush the buffers to disk
1711
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1712
state._dirblock_state)
1714
stat_value = os.lstat('a')
1715
packed_stat = dirstate.pack_stat(stat_value)
1716
link_or_sha1 = state.update_entry(entry, abspath='a',
1717
stat_value=stat_value)
1718
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1721
# The dirblock entry should not cache the file's sha1
1722
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1724
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1725
state._dirblock_state)
1726
mode = stat_value.st_mode
1727
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1730
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1731
state._dirblock_state)
1733
# If we do it again right away, we don't know if the file has changed
1734
# so we will re-read the file. Roll the clock back so the file is
1735
# guaranteed to look too new.
1736
state.adjust_time(-10)
1738
link_or_sha1 = state.update_entry(entry, abspath='a',
1739
stat_value=stat_value)
1740
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1741
('sha1', 'a'), ('is_exec', mode, False),
1743
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1745
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1746
state._dirblock_state)
1747
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1751
# However, if we move the clock forward so the file is considered
1752
# "stable", it should just cache the value.
1753
state.adjust_time(+20)
1754
link_or_sha1 = state.update_entry(entry, abspath='a',
1755
stat_value=stat_value)
1756
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1758
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1759
('sha1', 'a'), ('is_exec', mode, False),
1760
('sha1', 'a'), ('is_exec', mode, False),
1762
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1765
# Subsequent calls will just return the cached value
1766
link_or_sha1 = state.update_entry(entry, abspath='a',
1767
stat_value=stat_value)
1768
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1770
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1771
('sha1', 'a'), ('is_exec', mode, False),
1772
('sha1', 'a'), ('is_exec', mode, False),
1774
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1777
def test_update_entry_symlink(self):
1778
"""Update entry should read symlinks."""
1779
self.requireFeature(SymlinkFeature)
1780
state, entry = self.get_state_with_a()
1782
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1783
state._dirblock_state)
1784
os.symlink('target', 'a')
1786
state.adjust_time(-10) # Make the symlink look new
1787
stat_value = os.lstat('a')
1788
packed_stat = dirstate.pack_stat(stat_value)
1789
link_or_sha1 = state.update_entry(entry, abspath='a',
1790
stat_value=stat_value)
1791
self.assertEqual('target', link_or_sha1)
1792
self.assertEqual([('read_link', 'a', '')], state._log)
1793
# Dirblock is not updated (the link is too new)
1794
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1796
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1797
state._dirblock_state)
1799
# Because the stat_value looks new, we should re-read the target
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([('read_link', 'a', ''),
1804
('read_link', 'a', ''),
1806
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1808
state.adjust_time(+20) # Skip into the future, all files look old
1809
link_or_sha1 = state.update_entry(entry, abspath='a',
1810
stat_value=stat_value)
1811
self.assertEqual('target', link_or_sha1)
1812
# We need to re-read the link because only now can we cache it
1813
self.assertEqual([('read_link', 'a', ''),
1814
('read_link', 'a', ''),
1815
('read_link', 'a', ''),
1817
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1820
# Another call won't re-read the link
1821
self.assertEqual([('read_link', 'a', ''),
1822
('read_link', 'a', ''),
1823
('read_link', 'a', ''),
1825
link_or_sha1 = state.update_entry(entry, abspath='a',
1826
stat_value=stat_value)
1827
self.assertEqual('target', link_or_sha1)
1828
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1831
def do_update_entry(self, state, entry, abspath):
1832
stat_value = os.lstat(abspath)
1833
return state.update_entry(entry, abspath, stat_value)
1835
def test_update_entry_dir(self):
1836
state, entry = self.get_state_with_a()
1837
self.build_tree(['a/'])
1838
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1840
def test_update_entry_dir_unchanged(self):
1841
state, entry = self.get_state_with_a()
1842
self.build_tree(['a/'])
1843
state.adjust_time(+20)
1844
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1845
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1846
state._dirblock_state)
1848
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1849
state._dirblock_state)
1850
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1851
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1852
state._dirblock_state)
1854
def test_update_entry_file_unchanged(self):
1855
state, entry = self.get_state_with_a()
1856
self.build_tree(['a'])
1857
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1858
state.adjust_time(+20)
1859
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1860
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1861
state._dirblock_state)
1863
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1864
state._dirblock_state)
1865
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1866
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1867
state._dirblock_state)
1869
def create_and_test_file(self, state, entry):
1870
"""Create a file at 'a' and verify the state finds it.
1872
The state should already be versioning *something* at 'a'. This makes
1873
sure that state.update_entry recognizes it as a file.
1875
self.build_tree(['a'])
1876
stat_value = os.lstat('a')
1877
packed_stat = dirstate.pack_stat(stat_value)
1879
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1880
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1882
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1886
def create_and_test_dir(self, state, entry):
1887
"""Create a directory at 'a' and verify the state finds it.
1889
The state should already be versioning *something* at 'a'. This makes
1890
sure that state.update_entry recognizes it as a directory.
1892
self.build_tree(['a/'])
1893
stat_value = os.lstat('a')
1894
packed_stat = dirstate.pack_stat(stat_value)
1896
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1897
self.assertIs(None, link_or_sha1)
1898
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1902
def create_and_test_symlink(self, state, entry):
1903
"""Create a symlink at 'a' and verify the state finds it.
1905
The state should already be versioning *something* at 'a'. This makes
1906
sure that state.update_entry recognizes it as a symlink.
1908
This should not be called if this platform does not have symlink
1911
# caller should care about skipping test on platforms without symlinks
1912
os.symlink('path/to/foo', 'a')
1914
stat_value = os.lstat('a')
1915
packed_stat = dirstate.pack_stat(stat_value)
1917
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1918
self.assertEqual('path/to/foo', link_or_sha1)
1919
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1923
def test_update_file_to_dir(self):
1924
"""If a file changes to a directory we return None for the sha.
1925
We also update the inventory record.
1927
state, entry = self.get_state_with_a()
1928
# The file sha1 won't be cached unless the file is old
1929
state.adjust_time(+10)
1930
self.create_and_test_file(state, entry)
1932
self.create_and_test_dir(state, entry)
1934
def test_update_file_to_symlink(self):
1935
"""File becomes a symlink"""
1936
self.requireFeature(SymlinkFeature)
1937
state, entry = self.get_state_with_a()
1938
# The file sha1 won't be cached unless the file is old
1939
state.adjust_time(+10)
1940
self.create_and_test_file(state, entry)
1942
self.create_and_test_symlink(state, entry)
1944
def test_update_dir_to_file(self):
1945
"""Directory becoming a file updates the entry."""
1946
state, entry = self.get_state_with_a()
1947
# The file sha1 won't be cached unless the file is old
1948
state.adjust_time(+10)
1949
self.create_and_test_dir(state, entry)
1951
self.create_and_test_file(state, entry)
1953
def test_update_dir_to_symlink(self):
1954
"""Directory becomes a symlink"""
1955
self.requireFeature(SymlinkFeature)
1956
state, entry = self.get_state_with_a()
1957
# The symlink target won't be cached if it isn't old
1958
state.adjust_time(+10)
1959
self.create_and_test_dir(state, entry)
1961
self.create_and_test_symlink(state, entry)
1963
def test_update_symlink_to_file(self):
1964
"""Symlink becomes a file"""
1965
self.requireFeature(SymlinkFeature)
1966
state, entry = self.get_state_with_a()
1967
# The symlink and file info won't be cached unless old
1968
state.adjust_time(+10)
1969
self.create_and_test_symlink(state, entry)
1971
self.create_and_test_file(state, entry)
1973
def test_update_symlink_to_dir(self):
1974
"""Symlink becomes a directory"""
1975
self.requireFeature(SymlinkFeature)
1976
state, entry = self.get_state_with_a()
1977
# The symlink target won't be cached if it isn't old
1978
state.adjust_time(+10)
1979
self.create_and_test_symlink(state, entry)
1981
self.create_and_test_dir(state, entry)
1983
def test__is_executable_win32(self):
1984
state, entry = self.get_state_with_a()
1985
self.build_tree(['a'])
1987
# Make sure we are using the win32 implementation of _is_executable
1988
state._is_executable = state._is_executable_win32
1990
# The file on disk is not executable, but we are marking it as though
1991
# it is. With _is_executable_win32 we ignore what is on disk.
1992
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1994
stat_value = os.lstat('a')
1995
packed_stat = dirstate.pack_stat(stat_value)
1997
state.adjust_time(-10) # Make sure everything is new
1998
state.update_entry(entry, abspath='a', stat_value=stat_value)
2000
# The row is updated, but the executable bit stays set.
2001
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
2004
# Make the disk object look old enough to cache
2005
state.adjust_time(+20)
2006
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
2007
state.update_entry(entry, abspath='a', stat_value=stat_value)
2008
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
2011
class TestPackStat(TestCaseWithTransport):
2013
def assertPackStat(self, expected, stat_value):
2014
"""Check the packed and serialized form of a stat value."""
2015
self.assertEqual(expected, dirstate.pack_stat(stat_value))
2017
def test_pack_stat_int(self):
2018
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
2019
# Make sure that all parameters have an impact on the packed stat.
2020
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
2023
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
2024
st.st_mtime = 1172758620
2026
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
2027
st.st_ctime = 1172758630
2029
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2032
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
2033
st.st_ino = 6499540L
2035
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
2036
st.st_mode = 0100744
2038
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
2040
def test_pack_stat_float(self):
2041
"""On some platforms mtime and ctime are floats.
2043
Make sure we don't get warnings or errors, and that we ignore changes <
2046
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
2047
777L, 6499538L, 0100644)
2048
# These should all be the same as the integer counterparts
2049
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
2050
st.st_mtime = 1172758620.0
2052
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
2053
st.st_ctime = 1172758630.0
2055
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2056
# fractional seconds are discarded, so no change from above
2057
st.st_mtime = 1172758620.453
2058
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2059
st.st_ctime = 1172758630.228
2060
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2063
class TestBisect(TestCaseWithDirState):
2064
"""Test the ability to bisect into the disk format."""
2066
def assertBisect(self, expected_map, map_keys, state, paths):
2067
"""Assert that bisecting for paths returns the right result.
2069
:param expected_map: A map from key => entry value
2070
:param map_keys: The keys to expect for each path
2071
:param state: The DirState object.
2072
:param paths: A list of paths, these will automatically be split into
2073
(dir, name) tuples, and sorted according to how _bisect
2076
result = state._bisect(paths)
2077
# For now, results are just returned in whatever order we read them.
2078
# We could sort by (dir, name, file_id) or something like that, but in
2079
# the end it would still be fairly arbitrary, and we don't want the
2080
# extra overhead if we can avoid it. So sort everything to make sure
2082
self.assertEqual(len(map_keys), len(paths))
2084
for path, keys in zip(paths, map_keys):
2086
# This should not be present in the output
2088
expected[path] = sorted(expected_map[k] for k in keys)
2090
# The returned values are just arranged randomly based on when they
2091
# were read, for testing, make sure it is properly sorted.
2095
self.assertEqual(expected, result)
2097
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
2098
"""Assert that bisecting for dirbblocks returns the right result.
2100
:param expected_map: A map from key => expected values
2101
:param map_keys: A nested list of paths we expect to be returned.
2102
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
2103
:param state: The DirState object.
2104
:param paths: A list of directories
2106
result = state._bisect_dirblocks(paths)
2107
self.assertEqual(len(map_keys), len(paths))
2109
for path, keys in zip(paths, map_keys):
2111
# This should not be present in the output
2113
expected[path] = sorted(expected_map[k] for k in keys)
2117
self.assertEqual(expected, result)
2119
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
2120
"""Assert the return value of a recursive bisection.
2122
:param expected_map: A map from key => entry value
2123
:param map_keys: A list of paths we expect to be returned.
2124
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
2125
:param state: The DirState object.
2126
:param paths: A list of files and directories. It will be broken up
2127
into (dir, name) pairs and sorted before calling _bisect_recursive.
2130
for key in map_keys:
2131
entry = expected_map[key]
2132
dir_name_id, trees_info = entry
2133
expected[dir_name_id] = trees_info
2135
result = state._bisect_recursive(paths)
2137
self.assertEqual(expected, result)
2139
def test_bisect_each(self):
2140
"""Find a single record using bisect."""
2141
tree, state, expected = self.create_basic_dirstate()
2143
# Bisect should return the rows for the specified files.
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'])
2153
def test_bisect_multi(self):
2154
"""Bisect can be used to find multiple records at the same time."""
2155
tree, state, expected = self.create_basic_dirstate()
2156
# Bisect should be capable of finding multiple entries at the same time
2157
self.assertBisect(expected, [['a'], ['b'], ['f']],
2158
state, ['a', 'b', 'f'])
2159
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2160
state, ['f', 'b/d', 'b/d/e'])
2161
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2162
state, ['b', 'b-c', 'b/c'])
2164
def test_bisect_one_page(self):
2165
"""Test bisect when there is only 1 page to read"""
2166
tree, state, expected = self.create_basic_dirstate()
2167
state._bisect_page_size = 5000
2168
self.assertBisect(expected,[['']], state, [''])
2169
self.assertBisect(expected,[['a']], state, ['a'])
2170
self.assertBisect(expected,[['b']], state, ['b'])
2171
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2172
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2173
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2174
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2175
self.assertBisect(expected,[['f']], state, ['f'])
2176
self.assertBisect(expected,[['a'], ['b'], ['f']],
2177
state, ['a', 'b', 'f'])
2178
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2179
state, ['b/d', 'b/d/e', 'f'])
2180
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2181
state, ['b', 'b/c', 'b-c'])
2183
def test_bisect_duplicate_paths(self):
2184
"""When bisecting for a path, handle multiple entries."""
2185
tree, state, expected = self.create_duplicated_dirstate()
2187
# Now make sure that both records are properly returned.
2188
self.assertBisect(expected, [['']], state, [''])
2189
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2190
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2191
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2192
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2193
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2195
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2196
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2198
def test_bisect_page_size_too_small(self):
2199
"""If the page size is too small, we will auto increase it."""
2200
tree, state, expected = self.create_basic_dirstate()
2201
state._bisect_page_size = 50
2202
self.assertBisect(expected, [None], state, ['b/e'])
2203
self.assertBisect(expected, [['a']], state, ['a'])
2204
self.assertBisect(expected, [['b']], state, ['b'])
2205
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2206
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2207
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2208
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2209
self.assertBisect(expected, [['f']], state, ['f'])
2211
def test_bisect_missing(self):
2212
"""Test that bisect return None if it cannot find a path."""
2213
tree, state, expected = self.create_basic_dirstate()
2214
self.assertBisect(expected, [None], state, ['foo'])
2215
self.assertBisect(expected, [None], state, ['b/foo'])
2216
self.assertBisect(expected, [None], state, ['bar/foo'])
2217
self.assertBisect(expected, [None], state, ['b-c/foo'])
2219
self.assertBisect(expected, [['a'], None, ['b/d']],
2220
state, ['a', 'foo', 'b/d'])
2222
def test_bisect_rename(self):
2223
"""Check that we find a renamed row."""
2224
tree, state, expected = self.create_renamed_dirstate()
2226
# Search for the pre and post renamed entries
2227
self.assertBisect(expected, [['a']], state, ['a'])
2228
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2229
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2230
self.assertBisect(expected, [['h']], state, ['h'])
2232
# What about b/d/e? shouldn't that also get 2 directory entries?
2233
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2234
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2236
def test_bisect_dirblocks(self):
2237
tree, state, expected = self.create_duplicated_dirstate()
2238
self.assertBisectDirBlocks(expected,
2239
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2241
self.assertBisectDirBlocks(expected,
2242
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2243
self.assertBisectDirBlocks(expected,
2244
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2245
self.assertBisectDirBlocks(expected,
2246
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2247
['b/c', 'b/c2', 'b/d', 'b/d2'],
2248
['b/d/e', 'b/d/e2'],
2249
], state, ['', 'b', 'b/d'])
2251
def test_bisect_dirblocks_missing(self):
2252
tree, state, expected = self.create_basic_dirstate()
2253
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2254
state, ['b/d', 'b/e'])
2255
# Files don't show up in this search
2256
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2257
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2258
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2259
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2260
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2262
def test_bisect_recursive_each(self):
2263
tree, state, expected = self.create_basic_dirstate()
2264
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2265
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2266
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2267
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2268
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2270
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2272
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2276
def test_bisect_recursive_multiple(self):
2277
tree, state, expected = self.create_basic_dirstate()
2278
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2279
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2280
state, ['b/d', 'b/d/e'])
2282
def test_bisect_recursive_missing(self):
2283
tree, state, expected = self.create_basic_dirstate()
2284
self.assertBisectRecursive(expected, [], state, ['d'])
2285
self.assertBisectRecursive(expected, [], state, ['b/e'])
2286
self.assertBisectRecursive(expected, [], state, ['g'])
2287
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2289
def test_bisect_recursive_renamed(self):
2290
tree, state, expected = self.create_renamed_dirstate()
2292
# Looking for either renamed item should find the other
2293
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2294
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2295
# Looking in the containing directory should find the rename target,
2296
# and anything in a subdir of the renamed target.
2297
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2298
'b/d/e', 'b/g', 'h', 'h/e'],
2302
class TestDirstateValidation(TestCaseWithDirState):
2304
def test_validate_correct_dirstate(self):
2305
state = self.create_complex_dirstate()
2308
# and make sure we can also validate with a read lock
2315
def test_dirblock_not_sorted(self):
2316
tree, state, expected = self.create_renamed_dirstate()
2317
state._read_dirblocks_if_needed()
2318
last_dirblock = state._dirblocks[-1]
2319
# we're appending to the dirblock, but this name comes before some of
2320
# the existing names; that's wrong
2321
last_dirblock[1].append(
2322
(('h', 'aaaa', 'a-id'),
2323
[('a', '', 0, False, ''),
2324
('a', '', 0, False, '')]))
2325
e = self.assertRaises(AssertionError,
2327
self.assertContainsRe(str(e), 'not sorted')
2329
def test_dirblock_name_mismatch(self):
2330
tree, state, expected = self.create_renamed_dirstate()
2331
state._read_dirblocks_if_needed()
2332
last_dirblock = state._dirblocks[-1]
2333
# add an entry with the wrong directory name
2334
last_dirblock[1].append(
2336
[('a', '', 0, False, ''),
2337
('a', '', 0, False, '')]))
2338
e = self.assertRaises(AssertionError,
2340
self.assertContainsRe(str(e),
2341
"doesn't match directory name")
2343
def test_dirblock_missing_rename(self):
2344
tree, state, expected = self.create_renamed_dirstate()
2345
state._read_dirblocks_if_needed()
2346
last_dirblock = state._dirblocks[-1]
2347
# make another entry for a-id, without a correct 'r' pointer to
2348
# the real occurrence in the working tree
2349
last_dirblock[1].append(
2350
(('h', 'z', 'a-id'),
2351
[('a', '', 0, False, ''),
2352
('a', '', 0, False, '')]))
2353
e = self.assertRaises(AssertionError,
2355
self.assertContainsRe(str(e),
2356
'file a-id is absent in row')
2359
class TestDirstateTreeReference(TestCaseWithDirState):
2361
def test_reference_revision_is_none(self):
2362
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2363
subtree = self.make_branch_and_tree('tree/subtree',
2364
format='dirstate-with-subtree')
2365
subtree.set_root_id('subtree')
2366
tree.add_reference(subtree)
2368
state = dirstate.DirState.from_tree(tree, 'dirstate')
2369
key = ('', 'subtree', 'subtree')
2370
expected = ('', [(key,
2371
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2374
self.assertEqual(expected, state._find_block(key))
2379
class TestDiscardMergeParents(TestCaseWithDirState):
2381
def test_discard_no_parents(self):
2382
# This should be a no-op
2383
state = self.create_empty_dirstate()
2384
self.addCleanup(state.unlock)
2385
state._discard_merge_parents()
2388
def test_discard_one_parent(self):
2390
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2391
root_entry_direntry = ('', '', 'a-root-value'), [
2392
('d', '', 0, False, packed_stat),
2393
('d', '', 0, False, packed_stat),
2396
dirblocks.append(('', [root_entry_direntry]))
2397
dirblocks.append(('', []))
2399
state = self.create_empty_dirstate()
2400
self.addCleanup(state.unlock)
2401
state._set_data(['parent-id'], dirblocks[:])
2404
state._discard_merge_parents()
2406
self.assertEqual(dirblocks, state._dirblocks)
2408
def test_discard_simple(self):
2410
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2411
root_entry_direntry = ('', '', 'a-root-value'), [
2412
('d', '', 0, False, packed_stat),
2413
('d', '', 0, False, packed_stat),
2414
('d', '', 0, False, packed_stat),
2416
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2417
('d', '', 0, False, packed_stat),
2418
('d', '', 0, False, packed_stat),
2421
dirblocks.append(('', [root_entry_direntry]))
2422
dirblocks.append(('', []))
2424
state = self.create_empty_dirstate()
2425
self.addCleanup(state.unlock)
2426
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2429
# This should strip of the extra column
2430
state._discard_merge_parents()
2432
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2433
self.assertEqual(expected_dirblocks, state._dirblocks)
2435
def test_discard_absent(self):
2436
"""If entries are only in a merge, discard should remove the entries"""
2437
null_stat = dirstate.DirState.NULLSTAT
2438
present_dir = ('d', '', 0, False, null_stat)
2439
present_file = ('f', '', 0, False, null_stat)
2440
absent = dirstate.DirState.NULL_PARENT_DETAILS
2441
root_key = ('', '', 'a-root-value')
2442
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2443
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2444
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2445
('', [(file_in_merged_key,
2446
[absent, absent, present_file]),
2448
[present_file, present_file, present_file]),
2452
state = self.create_empty_dirstate()
2453
self.addCleanup(state.unlock)
2454
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2457
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2458
('', [(file_in_root_key,
2459
[present_file, present_file]),
2462
state._discard_merge_parents()
2464
self.assertEqual(exp_dirblocks, state._dirblocks)
2466
def test_discard_renamed(self):
2467
null_stat = dirstate.DirState.NULLSTAT
2468
present_dir = ('d', '', 0, False, null_stat)
2469
present_file = ('f', '', 0, False, null_stat)
2470
absent = dirstate.DirState.NULL_PARENT_DETAILS
2471
root_key = ('', '', 'a-root-value')
2472
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2473
# Renamed relative to parent
2474
file_rename_s_key = ('', 'file-s', 'b-file-id')
2475
file_rename_t_key = ('', 'file-t', 'b-file-id')
2476
# And one that is renamed between the parents, but absent in this
2477
key_in_1 = ('', 'file-in-1', 'c-file-id')
2478
key_in_2 = ('', 'file-in-2', 'c-file-id')
2481
('', [(root_key, [present_dir, present_dir, present_dir])]),
2483
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2485
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2487
[present_file, present_file, present_file]),
2489
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2491
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2495
('', [(root_key, [present_dir, present_dir])]),
2496
('', [(key_in_1, [absent, present_file]),
2497
(file_in_root_key, [present_file, present_file]),
2498
(file_rename_t_key, [present_file, absent]),
2501
state = self.create_empty_dirstate()
2502
self.addCleanup(state.unlock)
2503
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2506
state._discard_merge_parents()
2508
self.assertEqual(exp_dirblocks, state._dirblocks)
2510
def test_discard_all_subdir(self):
2511
null_stat = dirstate.DirState.NULLSTAT
2512
present_dir = ('d', '', 0, False, null_stat)
2513
present_file = ('f', '', 0, False, null_stat)
2514
absent = dirstate.DirState.NULL_PARENT_DETAILS
2515
root_key = ('', '', 'a-root-value')
2516
subdir_key = ('', 'sub', 'dir-id')
2517
child1_key = ('sub', 'child1', 'child1-id')
2518
child2_key = ('sub', 'child2', 'child2-id')
2519
child3_key = ('sub', 'child3', 'child3-id')
2522
('', [(root_key, [present_dir, present_dir, present_dir])]),
2523
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2524
('sub', [(child1_key, [absent, absent, present_file]),
2525
(child2_key, [absent, absent, present_file]),
2526
(child3_key, [absent, absent, present_file]),
2530
('', [(root_key, [present_dir, present_dir])]),
2531
('', [(subdir_key, [present_dir, present_dir])]),
2534
state = self.create_empty_dirstate()
2535
self.addCleanup(state.unlock)
2536
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2539
state._discard_merge_parents()
2541
self.assertEqual(exp_dirblocks, state._dirblocks)
2544
class Test_InvEntryToDetails(TestCaseWithDirState):
2546
def assertDetails(self, expected, inv_entry):
2547
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2548
self.assertEqual(expected, details)
2549
# details should always allow join() and always be a plain str when
2551
(minikind, fingerprint, size, executable, tree_data) = details
2552
self.assertIsInstance(minikind, str)
2553
self.assertIsInstance(fingerprint, str)
2554
self.assertIsInstance(tree_data, str)
2556
def test_unicode_symlink(self):
2557
# In general, the code base doesn't support a target that contains
2558
# non-ascii characters. So we just assert tha
2559
inv_entry = inventory.InventoryLink('link-file-id', 'name',
2561
inv_entry.revision = 'link-revision-id'
2562
inv_entry.symlink_target = u'link-target'
2563
details = self.assertDetails(('l', 'link-target', 0, False,
2564
'link-revision-id'), inv_entry)