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_update_entry(self):
1681
state, entry = self.get_state_with_a()
1682
self.build_tree(['a'])
1683
# Add one where we don't provide the stat or sha already
1684
self.assertEqual(('', 'a', 'a-id'), entry[0])
1685
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1687
# Flush the buffers to disk
1689
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1690
state._dirblock_state)
1692
stat_value = os.lstat('a')
1693
packed_stat = dirstate.pack_stat(stat_value)
1694
link_or_sha1 = state.update_entry(entry, abspath='a',
1695
stat_value=stat_value)
1696
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1699
# The dirblock entry should not cache the file's sha1
1700
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1702
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1703
state._dirblock_state)
1704
mode = stat_value.st_mode
1705
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1708
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1709
state._dirblock_state)
1711
# If we do it again right away, we don't know if the file has changed
1712
# so we will re-read the file. Roll the clock back so the file is
1713
# guaranteed to look too new.
1714
state.adjust_time(-10)
1716
link_or_sha1 = state.update_entry(entry, abspath='a',
1717
stat_value=stat_value)
1718
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1719
('sha1', 'a'), ('is_exec', mode, False),
1721
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1723
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1724
state._dirblock_state)
1725
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1729
# However, if we move the clock forward so the file is considered
1730
# "stable", it should just cache the value.
1731
state.adjust_time(+20)
1732
link_or_sha1 = state.update_entry(entry, abspath='a',
1733
stat_value=stat_value)
1734
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1736
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1737
('sha1', 'a'), ('is_exec', mode, False),
1738
('sha1', 'a'), ('is_exec', mode, False),
1740
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1743
# Subsequent calls will just return the cached value
1744
link_or_sha1 = state.update_entry(entry, abspath='a',
1745
stat_value=stat_value)
1746
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1748
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1749
('sha1', 'a'), ('is_exec', mode, False),
1750
('sha1', 'a'), ('is_exec', mode, False),
1752
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1755
def test_update_entry_symlink(self):
1756
"""Update entry should read symlinks."""
1757
self.requireFeature(SymlinkFeature)
1758
state, entry = self.get_state_with_a()
1760
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1761
state._dirblock_state)
1762
os.symlink('target', 'a')
1764
state.adjust_time(-10) # Make the symlink look new
1765
stat_value = os.lstat('a')
1766
packed_stat = dirstate.pack_stat(stat_value)
1767
link_or_sha1 = state.update_entry(entry, abspath='a',
1768
stat_value=stat_value)
1769
self.assertEqual('target', link_or_sha1)
1770
self.assertEqual([('read_link', 'a', '')], state._log)
1771
# Dirblock is not updated (the link is too new)
1772
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1774
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1775
state._dirblock_state)
1777
# Because the stat_value looks new, we should re-read the target
1778
link_or_sha1 = state.update_entry(entry, abspath='a',
1779
stat_value=stat_value)
1780
self.assertEqual('target', link_or_sha1)
1781
self.assertEqual([('read_link', 'a', ''),
1782
('read_link', 'a', ''),
1784
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1786
state.adjust_time(+20) # Skip into the future, all files look old
1787
link_or_sha1 = state.update_entry(entry, abspath='a',
1788
stat_value=stat_value)
1789
self.assertEqual('target', link_or_sha1)
1790
# We need to re-read the link because only now can we cache it
1791
self.assertEqual([('read_link', 'a', ''),
1792
('read_link', 'a', ''),
1793
('read_link', 'a', ''),
1795
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1798
# Another call won't re-read the link
1799
self.assertEqual([('read_link', 'a', ''),
1800
('read_link', 'a', ''),
1801
('read_link', 'a', ''),
1803
link_or_sha1 = state.update_entry(entry, abspath='a',
1804
stat_value=stat_value)
1805
self.assertEqual('target', link_or_sha1)
1806
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1809
def do_update_entry(self, state, entry, abspath):
1810
stat_value = os.lstat(abspath)
1811
return state.update_entry(entry, abspath, stat_value)
1813
def test_update_entry_dir(self):
1814
state, entry = self.get_state_with_a()
1815
self.build_tree(['a/'])
1816
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1818
def test_update_entry_dir_unchanged(self):
1819
state, entry = self.get_state_with_a()
1820
self.build_tree(['a/'])
1821
state.adjust_time(+20)
1822
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1823
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1824
state._dirblock_state)
1826
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1827
state._dirblock_state)
1828
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1829
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1830
state._dirblock_state)
1832
def test_update_entry_file_unchanged(self):
1833
state, entry = self.get_state_with_a()
1834
self.build_tree(['a'])
1835
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1836
state.adjust_time(+20)
1837
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1838
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1839
state._dirblock_state)
1841
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1842
state._dirblock_state)
1843
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1844
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1845
state._dirblock_state)
1847
def create_and_test_file(self, state, entry):
1848
"""Create a file at 'a' and verify the state finds it.
1850
The state should already be versioning *something* at 'a'. This makes
1851
sure that state.update_entry recognizes it as a file.
1853
self.build_tree(['a'])
1854
stat_value = os.lstat('a')
1855
packed_stat = dirstate.pack_stat(stat_value)
1857
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1858
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1860
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1864
def create_and_test_dir(self, state, entry):
1865
"""Create a directory at 'a' and verify the state finds it.
1867
The state should already be versioning *something* at 'a'. This makes
1868
sure that state.update_entry recognizes it as a directory.
1870
self.build_tree(['a/'])
1871
stat_value = os.lstat('a')
1872
packed_stat = dirstate.pack_stat(stat_value)
1874
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1875
self.assertIs(None, link_or_sha1)
1876
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1880
def create_and_test_symlink(self, state, entry):
1881
"""Create a symlink at 'a' and verify the state finds it.
1883
The state should already be versioning *something* at 'a'. This makes
1884
sure that state.update_entry recognizes it as a symlink.
1886
This should not be called if this platform does not have symlink
1889
# caller should care about skipping test on platforms without symlinks
1890
os.symlink('path/to/foo', 'a')
1892
stat_value = os.lstat('a')
1893
packed_stat = dirstate.pack_stat(stat_value)
1895
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1896
self.assertEqual('path/to/foo', link_or_sha1)
1897
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1901
def test_update_file_to_dir(self):
1902
"""If a file changes to a directory we return None for the sha.
1903
We also update the inventory record.
1905
state, entry = self.get_state_with_a()
1906
# The file sha1 won't be cached unless the file is old
1907
state.adjust_time(+10)
1908
self.create_and_test_file(state, entry)
1910
self.create_and_test_dir(state, entry)
1912
def test_update_file_to_symlink(self):
1913
"""File becomes a symlink"""
1914
self.requireFeature(SymlinkFeature)
1915
state, entry = self.get_state_with_a()
1916
# The file sha1 won't be cached unless the file is old
1917
state.adjust_time(+10)
1918
self.create_and_test_file(state, entry)
1920
self.create_and_test_symlink(state, entry)
1922
def test_update_dir_to_file(self):
1923
"""Directory becoming a file updates the entry."""
1924
state, entry = self.get_state_with_a()
1925
# The file sha1 won't be cached unless the file is old
1926
state.adjust_time(+10)
1927
self.create_and_test_dir(state, entry)
1929
self.create_and_test_file(state, entry)
1931
def test_update_dir_to_symlink(self):
1932
"""Directory becomes a symlink"""
1933
self.requireFeature(SymlinkFeature)
1934
state, entry = self.get_state_with_a()
1935
# The symlink target won't be cached if it isn't old
1936
state.adjust_time(+10)
1937
self.create_and_test_dir(state, entry)
1939
self.create_and_test_symlink(state, entry)
1941
def test_update_symlink_to_file(self):
1942
"""Symlink becomes a file"""
1943
self.requireFeature(SymlinkFeature)
1944
state, entry = self.get_state_with_a()
1945
# The symlink and file info won't be cached unless old
1946
state.adjust_time(+10)
1947
self.create_and_test_symlink(state, entry)
1949
self.create_and_test_file(state, entry)
1951
def test_update_symlink_to_dir(self):
1952
"""Symlink becomes a directory"""
1953
self.requireFeature(SymlinkFeature)
1954
state, entry = self.get_state_with_a()
1955
# The symlink target won't be cached if it isn't old
1956
state.adjust_time(+10)
1957
self.create_and_test_symlink(state, entry)
1959
self.create_and_test_dir(state, entry)
1961
def test__is_executable_win32(self):
1962
state, entry = self.get_state_with_a()
1963
self.build_tree(['a'])
1965
# Make sure we are using the win32 implementation of _is_executable
1966
state._is_executable = state._is_executable_win32
1968
# The file on disk is not executable, but we are marking it as though
1969
# it is. With _is_executable_win32 we ignore what is on disk.
1970
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1972
stat_value = os.lstat('a')
1973
packed_stat = dirstate.pack_stat(stat_value)
1975
state.adjust_time(-10) # Make sure everything is new
1976
state.update_entry(entry, abspath='a', stat_value=stat_value)
1978
# The row is updated, but the executable bit stays set.
1979
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1982
# Make the disk object look old enough to cache
1983
state.adjust_time(+20)
1984
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1985
state.update_entry(entry, abspath='a', stat_value=stat_value)
1986
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1989
class TestPackStat(TestCaseWithTransport):
1991
def assertPackStat(self, expected, stat_value):
1992
"""Check the packed and serialized form of a stat value."""
1993
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1995
def test_pack_stat_int(self):
1996
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1997
# Make sure that all parameters have an impact on the packed stat.
1998
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
2001
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
2002
st.st_mtime = 1172758620
2004
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
2005
st.st_ctime = 1172758630
2007
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2010
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
2011
st.st_ino = 6499540L
2013
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
2014
st.st_mode = 0100744
2016
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
2018
def test_pack_stat_float(self):
2019
"""On some platforms mtime and ctime are floats.
2021
Make sure we don't get warnings or errors, and that we ignore changes <
2024
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
2025
777L, 6499538L, 0100644)
2026
# These should all be the same as the integer counterparts
2027
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
2028
st.st_mtime = 1172758620.0
2030
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
2031
st.st_ctime = 1172758630.0
2033
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2034
# fractional seconds are discarded, so no change from above
2035
st.st_mtime = 1172758620.453
2036
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2037
st.st_ctime = 1172758630.228
2038
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
2041
class TestBisect(TestCaseWithDirState):
2042
"""Test the ability to bisect into the disk format."""
2044
def assertBisect(self, expected_map, map_keys, state, paths):
2045
"""Assert that bisecting for paths returns the right result.
2047
:param expected_map: A map from key => entry value
2048
:param map_keys: The keys to expect for each path
2049
:param state: The DirState object.
2050
:param paths: A list of paths, these will automatically be split into
2051
(dir, name) tuples, and sorted according to how _bisect
2054
result = state._bisect(paths)
2055
# For now, results are just returned in whatever order we read them.
2056
# We could sort by (dir, name, file_id) or something like that, but in
2057
# the end it would still be fairly arbitrary, and we don't want the
2058
# extra overhead if we can avoid it. So sort everything to make sure
2060
self.assertEqual(len(map_keys), len(paths))
2062
for path, keys in zip(paths, map_keys):
2064
# This should not be present in the output
2066
expected[path] = sorted(expected_map[k] for k in keys)
2068
# The returned values are just arranged randomly based on when they
2069
# were read, for testing, make sure it is properly sorted.
2073
self.assertEqual(expected, result)
2075
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
2076
"""Assert that bisecting for dirbblocks returns the right result.
2078
:param expected_map: A map from key => expected values
2079
:param map_keys: A nested list of paths we expect to be returned.
2080
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
2081
:param state: The DirState object.
2082
:param paths: A list of directories
2084
result = state._bisect_dirblocks(paths)
2085
self.assertEqual(len(map_keys), len(paths))
2087
for path, keys in zip(paths, map_keys):
2089
# This should not be present in the output
2091
expected[path] = sorted(expected_map[k] for k in keys)
2095
self.assertEqual(expected, result)
2097
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
2098
"""Assert the return value of a recursive bisection.
2100
:param expected_map: A map from key => entry value
2101
:param map_keys: A list of paths we expect to be returned.
2102
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
2103
:param state: The DirState object.
2104
:param paths: A list of files and directories. It will be broken up
2105
into (dir, name) pairs and sorted before calling _bisect_recursive.
2108
for key in map_keys:
2109
entry = expected_map[key]
2110
dir_name_id, trees_info = entry
2111
expected[dir_name_id] = trees_info
2113
result = state._bisect_recursive(paths)
2115
self.assertEqual(expected, result)
2117
def test_bisect_each(self):
2118
"""Find a single record using bisect."""
2119
tree, state, expected = self.create_basic_dirstate()
2121
# Bisect should return the rows for the specified files.
2122
self.assertBisect(expected, [['']], state, [''])
2123
self.assertBisect(expected, [['a']], state, ['a'])
2124
self.assertBisect(expected, [['b']], state, ['b'])
2125
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2126
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2127
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2128
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2129
self.assertBisect(expected, [['f']], state, ['f'])
2131
def test_bisect_multi(self):
2132
"""Bisect can be used to find multiple records at the same time."""
2133
tree, state, expected = self.create_basic_dirstate()
2134
# Bisect should be capable of finding multiple entries at the same time
2135
self.assertBisect(expected, [['a'], ['b'], ['f']],
2136
state, ['a', 'b', 'f'])
2137
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
2138
state, ['f', 'b/d', 'b/d/e'])
2139
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
2140
state, ['b', 'b-c', 'b/c'])
2142
def test_bisect_one_page(self):
2143
"""Test bisect when there is only 1 page to read"""
2144
tree, state, expected = self.create_basic_dirstate()
2145
state._bisect_page_size = 5000
2146
self.assertBisect(expected,[['']], state, [''])
2147
self.assertBisect(expected,[['a']], state, ['a'])
2148
self.assertBisect(expected,[['b']], state, ['b'])
2149
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2150
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2151
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2152
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2153
self.assertBisect(expected,[['f']], state, ['f'])
2154
self.assertBisect(expected,[['a'], ['b'], ['f']],
2155
state, ['a', 'b', 'f'])
2156
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2157
state, ['b/d', 'b/d/e', 'f'])
2158
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2159
state, ['b', 'b/c', 'b-c'])
2161
def test_bisect_duplicate_paths(self):
2162
"""When bisecting for a path, handle multiple entries."""
2163
tree, state, expected = self.create_duplicated_dirstate()
2165
# Now make sure that both records are properly returned.
2166
self.assertBisect(expected, [['']], state, [''])
2167
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2168
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2169
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2170
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2171
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2173
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2174
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2176
def test_bisect_page_size_too_small(self):
2177
"""If the page size is too small, we will auto increase it."""
2178
tree, state, expected = self.create_basic_dirstate()
2179
state._bisect_page_size = 50
2180
self.assertBisect(expected, [None], state, ['b/e'])
2181
self.assertBisect(expected, [['a']], state, ['a'])
2182
self.assertBisect(expected, [['b']], state, ['b'])
2183
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2184
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2185
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2186
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2187
self.assertBisect(expected, [['f']], state, ['f'])
2189
def test_bisect_missing(self):
2190
"""Test that bisect return None if it cannot find a path."""
2191
tree, state, expected = self.create_basic_dirstate()
2192
self.assertBisect(expected, [None], state, ['foo'])
2193
self.assertBisect(expected, [None], state, ['b/foo'])
2194
self.assertBisect(expected, [None], state, ['bar/foo'])
2195
self.assertBisect(expected, [None], state, ['b-c/foo'])
2197
self.assertBisect(expected, [['a'], None, ['b/d']],
2198
state, ['a', 'foo', 'b/d'])
2200
def test_bisect_rename(self):
2201
"""Check that we find a renamed row."""
2202
tree, state, expected = self.create_renamed_dirstate()
2204
# Search for the pre and post renamed entries
2205
self.assertBisect(expected, [['a']], state, ['a'])
2206
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2207
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2208
self.assertBisect(expected, [['h']], state, ['h'])
2210
# What about b/d/e? shouldn't that also get 2 directory entries?
2211
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2212
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2214
def test_bisect_dirblocks(self):
2215
tree, state, expected = self.create_duplicated_dirstate()
2216
self.assertBisectDirBlocks(expected,
2217
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2219
self.assertBisectDirBlocks(expected,
2220
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2221
self.assertBisectDirBlocks(expected,
2222
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2223
self.assertBisectDirBlocks(expected,
2224
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2225
['b/c', 'b/c2', 'b/d', 'b/d2'],
2226
['b/d/e', 'b/d/e2'],
2227
], state, ['', 'b', 'b/d'])
2229
def test_bisect_dirblocks_missing(self):
2230
tree, state, expected = self.create_basic_dirstate()
2231
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2232
state, ['b/d', 'b/e'])
2233
# Files don't show up in this search
2234
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2235
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2236
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2237
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2238
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2240
def test_bisect_recursive_each(self):
2241
tree, state, expected = self.create_basic_dirstate()
2242
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2243
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2244
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2245
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2246
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2248
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2250
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2254
def test_bisect_recursive_multiple(self):
2255
tree, state, expected = self.create_basic_dirstate()
2256
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2257
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2258
state, ['b/d', 'b/d/e'])
2260
def test_bisect_recursive_missing(self):
2261
tree, state, expected = self.create_basic_dirstate()
2262
self.assertBisectRecursive(expected, [], state, ['d'])
2263
self.assertBisectRecursive(expected, [], state, ['b/e'])
2264
self.assertBisectRecursive(expected, [], state, ['g'])
2265
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2267
def test_bisect_recursive_renamed(self):
2268
tree, state, expected = self.create_renamed_dirstate()
2270
# Looking for either renamed item should find the other
2271
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2272
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2273
# Looking in the containing directory should find the rename target,
2274
# and anything in a subdir of the renamed target.
2275
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2276
'b/d/e', 'b/g', 'h', 'h/e'],
2280
class TestDirstateValidation(TestCaseWithDirState):
2282
def test_validate_correct_dirstate(self):
2283
state = self.create_complex_dirstate()
2286
# and make sure we can also validate with a read lock
2293
def test_dirblock_not_sorted(self):
2294
tree, state, expected = self.create_renamed_dirstate()
2295
state._read_dirblocks_if_needed()
2296
last_dirblock = state._dirblocks[-1]
2297
# we're appending to the dirblock, but this name comes before some of
2298
# the existing names; that's wrong
2299
last_dirblock[1].append(
2300
(('h', 'aaaa', 'a-id'),
2301
[('a', '', 0, False, ''),
2302
('a', '', 0, False, '')]))
2303
e = self.assertRaises(AssertionError,
2305
self.assertContainsRe(str(e), 'not sorted')
2307
def test_dirblock_name_mismatch(self):
2308
tree, state, expected = self.create_renamed_dirstate()
2309
state._read_dirblocks_if_needed()
2310
last_dirblock = state._dirblocks[-1]
2311
# add an entry with the wrong directory name
2312
last_dirblock[1].append(
2314
[('a', '', 0, False, ''),
2315
('a', '', 0, False, '')]))
2316
e = self.assertRaises(AssertionError,
2318
self.assertContainsRe(str(e),
2319
"doesn't match directory name")
2321
def test_dirblock_missing_rename(self):
2322
tree, state, expected = self.create_renamed_dirstate()
2323
state._read_dirblocks_if_needed()
2324
last_dirblock = state._dirblocks[-1]
2325
# make another entry for a-id, without a correct 'r' pointer to
2326
# the real occurrence in the working tree
2327
last_dirblock[1].append(
2328
(('h', 'z', 'a-id'),
2329
[('a', '', 0, False, ''),
2330
('a', '', 0, False, '')]))
2331
e = self.assertRaises(AssertionError,
2333
self.assertContainsRe(str(e),
2334
'file a-id is absent in row')
2337
class TestDirstateTreeReference(TestCaseWithDirState):
2339
def test_reference_revision_is_none(self):
2340
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2341
subtree = self.make_branch_and_tree('tree/subtree',
2342
format='dirstate-with-subtree')
2343
subtree.set_root_id('subtree')
2344
tree.add_reference(subtree)
2346
state = dirstate.DirState.from_tree(tree, 'dirstate')
2347
key = ('', 'subtree', 'subtree')
2348
expected = ('', [(key,
2349
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2352
self.assertEqual(expected, state._find_block(key))
2357
class TestDiscardMergeParents(TestCaseWithDirState):
2359
def test_discard_no_parents(self):
2360
# This should be a no-op
2361
state = self.create_empty_dirstate()
2362
self.addCleanup(state.unlock)
2363
state._discard_merge_parents()
2366
def test_discard_one_parent(self):
2368
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2369
root_entry_direntry = ('', '', 'a-root-value'), [
2370
('d', '', 0, False, packed_stat),
2371
('d', '', 0, False, packed_stat),
2374
dirblocks.append(('', [root_entry_direntry]))
2375
dirblocks.append(('', []))
2377
state = self.create_empty_dirstate()
2378
self.addCleanup(state.unlock)
2379
state._set_data(['parent-id'], dirblocks[:])
2382
state._discard_merge_parents()
2384
self.assertEqual(dirblocks, state._dirblocks)
2386
def test_discard_simple(self):
2388
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2389
root_entry_direntry = ('', '', 'a-root-value'), [
2390
('d', '', 0, False, packed_stat),
2391
('d', '', 0, False, packed_stat),
2392
('d', '', 0, False, packed_stat),
2394
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2395
('d', '', 0, False, packed_stat),
2396
('d', '', 0, False, packed_stat),
2399
dirblocks.append(('', [root_entry_direntry]))
2400
dirblocks.append(('', []))
2402
state = self.create_empty_dirstate()
2403
self.addCleanup(state.unlock)
2404
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2407
# This should strip of the extra column
2408
state._discard_merge_parents()
2410
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2411
self.assertEqual(expected_dirblocks, state._dirblocks)
2413
def test_discard_absent(self):
2414
"""If entries are only in a merge, discard should remove the entries"""
2415
null_stat = dirstate.DirState.NULLSTAT
2416
present_dir = ('d', '', 0, False, null_stat)
2417
present_file = ('f', '', 0, False, null_stat)
2418
absent = dirstate.DirState.NULL_PARENT_DETAILS
2419
root_key = ('', '', 'a-root-value')
2420
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2421
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2422
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2423
('', [(file_in_merged_key,
2424
[absent, absent, present_file]),
2426
[present_file, present_file, present_file]),
2430
state = self.create_empty_dirstate()
2431
self.addCleanup(state.unlock)
2432
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2435
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2436
('', [(file_in_root_key,
2437
[present_file, present_file]),
2440
state._discard_merge_parents()
2442
self.assertEqual(exp_dirblocks, state._dirblocks)
2444
def test_discard_renamed(self):
2445
null_stat = dirstate.DirState.NULLSTAT
2446
present_dir = ('d', '', 0, False, null_stat)
2447
present_file = ('f', '', 0, False, null_stat)
2448
absent = dirstate.DirState.NULL_PARENT_DETAILS
2449
root_key = ('', '', 'a-root-value')
2450
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2451
# Renamed relative to parent
2452
file_rename_s_key = ('', 'file-s', 'b-file-id')
2453
file_rename_t_key = ('', 'file-t', 'b-file-id')
2454
# And one that is renamed between the parents, but absent in this
2455
key_in_1 = ('', 'file-in-1', 'c-file-id')
2456
key_in_2 = ('', 'file-in-2', 'c-file-id')
2459
('', [(root_key, [present_dir, present_dir, present_dir])]),
2461
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2463
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2465
[present_file, present_file, present_file]),
2467
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2469
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2473
('', [(root_key, [present_dir, present_dir])]),
2474
('', [(key_in_1, [absent, present_file]),
2475
(file_in_root_key, [present_file, present_file]),
2476
(file_rename_t_key, [present_file, absent]),
2479
state = self.create_empty_dirstate()
2480
self.addCleanup(state.unlock)
2481
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2484
state._discard_merge_parents()
2486
self.assertEqual(exp_dirblocks, state._dirblocks)
2488
def test_discard_all_subdir(self):
2489
null_stat = dirstate.DirState.NULLSTAT
2490
present_dir = ('d', '', 0, False, null_stat)
2491
present_file = ('f', '', 0, False, null_stat)
2492
absent = dirstate.DirState.NULL_PARENT_DETAILS
2493
root_key = ('', '', 'a-root-value')
2494
subdir_key = ('', 'sub', 'dir-id')
2495
child1_key = ('sub', 'child1', 'child1-id')
2496
child2_key = ('sub', 'child2', 'child2-id')
2497
child3_key = ('sub', 'child3', 'child3-id')
2500
('', [(root_key, [present_dir, present_dir, present_dir])]),
2501
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2502
('sub', [(child1_key, [absent, absent, present_file]),
2503
(child2_key, [absent, absent, present_file]),
2504
(child3_key, [absent, absent, present_file]),
2508
('', [(root_key, [present_dir, present_dir])]),
2509
('', [(subdir_key, [present_dir, present_dir])]),
2512
state = self.create_empty_dirstate()
2513
self.addCleanup(state.unlock)
2514
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2517
state._discard_merge_parents()
2519
self.assertEqual(exp_dirblocks, state._dirblocks)
2522
class Test_InvEntryToDetails(TestCaseWithDirState):
2524
def assertDetails(self, expected, inv_entry):
2525
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2526
self.assertEqual(expected, details)
2527
# details should always allow join() and always be a plain str when
2529
(minikind, fingerprint, size, executable, tree_data) = details
2530
self.assertIsInstance(minikind, str)
2531
self.assertIsInstance(fingerprint, str)
2532
self.assertIsInstance(tree_data, str)
2534
def test_unicode_symlink(self):
2535
# In general, the code base doesn't support a target that contains
2536
# non-ascii characters. So we just assert tha
2537
inv_entry = inventory.InventoryLink('link-file-id', 'name',
2539
inv_entry.revision = 'link-revision-id'
2540
inv_entry.symlink_target = u'link-target'
2541
details = self.assertDetails(('l', 'link-target', 0, False,
2542
'link-revision-id'), inv_entry)