1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
28
from bzrlib.memorytree import MemoryTree
29
from bzrlib.osutils import has_symlinks
30
from bzrlib.tests import (
32
TestCaseWithTransport,
39
# general checks for NOT_IN_MEMORY error conditions.
40
# set_path_id on a NOT_IN_MEMORY dirstate
41
# set_path_id unicode support
42
# set_path_id setting id of a path not root
43
# set_path_id setting id when there are parents without the id in the parents
44
# set_path_id setting id when there are parents with the id in the parents
45
# set_path_id setting id when state is not in memory
46
# set_path_id setting id when state is in memory unmodified
47
# set_path_id setting id when state is in memory modified
50
class TestCaseWithDirState(TestCaseWithTransport):
51
"""Helper functions for creating DirState objects with various content."""
53
def create_empty_dirstate(self):
54
"""Return a locked but empty dirstate"""
55
state = dirstate.DirState.initialize('dirstate')
58
def create_dirstate_with_root(self):
59
"""Return a write-locked state with a single root entry."""
60
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
61
root_entry_direntry = ('', '', 'a-root-value'), [
62
('d', '', 0, False, packed_stat),
65
dirblocks.append(('', [root_entry_direntry]))
66
dirblocks.append(('', []))
67
state = self.create_empty_dirstate()
69
state._set_data([], dirblocks)
76
def create_dirstate_with_root_and_subdir(self):
77
"""Return a locked DirState with a root and a subdir"""
78
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
79
subdir_entry = ('', 'subdir', 'subdir-id'), [
80
('d', '', 0, False, packed_stat),
82
state = self.create_dirstate_with_root()
84
dirblocks = list(state._dirblocks)
85
dirblocks[1][1].append(subdir_entry)
86
state._set_data([], dirblocks)
92
def create_complex_dirstate(self):
93
"""This dirstate contains multiple files and directories.
103
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
105
Notice that a/e is an empty directory.
107
:return: The dirstate, still write-locked.
109
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
110
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
111
root_entry = ('', '', 'a-root-value'), [
112
('d', '', 0, False, packed_stat),
114
a_entry = ('', 'a', 'a-dir'), [
115
('d', '', 0, False, packed_stat),
117
b_entry = ('', 'b', 'b-dir'), [
118
('d', '', 0, False, packed_stat),
120
c_entry = ('', 'c', 'c-file'), [
121
('f', null_sha, 10, False, packed_stat),
123
d_entry = ('', 'd', 'd-file'), [
124
('f', null_sha, 20, False, packed_stat),
126
e_entry = ('a', 'e', 'e-dir'), [
127
('d', '', 0, False, packed_stat),
129
f_entry = ('a', 'f', 'f-file'), [
130
('f', null_sha, 30, False, packed_stat),
132
g_entry = ('b', 'g', 'g-file'), [
133
('f', null_sha, 30, False, packed_stat),
135
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
136
('f', null_sha, 40, False, packed_stat),
139
dirblocks.append(('', [root_entry]))
140
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
141
dirblocks.append(('a', [e_entry, f_entry]))
142
dirblocks.append(('b', [g_entry, h_entry]))
143
state = dirstate.DirState.initialize('dirstate')
146
state._set_data([], dirblocks)
152
def check_state_with_reopen(self, expected_result, state):
153
"""Check that state has current state expected_result.
155
This will check the current state, open the file anew and check it
157
This function expects the current state to be locked for writing, and
158
will unlock it before re-opening.
159
This is required because we can't open a lock_read() while something
160
else has a lock_write().
161
write => mutually exclusive lock
164
# The state should already be write locked, since we just had to do
165
# some operation to get here.
166
assert state._lock_token is not None
168
self.assertEqual(expected_result[0], state.get_parent_ids())
169
# there should be no ghosts in this tree.
170
self.assertEqual([], state.get_ghosts())
171
# there should be one fileid in this tree - the root of the tree.
172
self.assertEqual(expected_result[1], list(state._iter_entries()))
177
state = dirstate.DirState.on_file('dirstate')
180
self.assertEqual(expected_result[1], list(state._iter_entries()))
184
def create_basic_dirstate(self):
185
"""Create a dirstate with a few files and directories.
195
tree = self.make_branch_and_tree('tree')
196
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
197
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
198
self.build_tree(['tree/' + p for p in paths])
199
tree.set_root_id('TREE_ROOT')
200
tree.add([p.rstrip('/') for p in paths], file_ids)
201
tree.commit('initial', rev_id='rev-1')
202
revision_id = 'rev-1'
203
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
204
t = self.get_transport('tree')
205
a_text = t.get_bytes('a')
206
a_sha = osutils.sha_string(a_text)
208
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
209
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
210
c_text = t.get_bytes('b/c')
211
c_sha = osutils.sha_string(c_text)
213
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
214
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
215
e_text = t.get_bytes('b/d/e')
216
e_sha = osutils.sha_string(e_text)
218
b_c_text = t.get_bytes('b-c')
219
b_c_sha = osutils.sha_string(b_c_text)
220
b_c_len = len(b_c_text)
221
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
222
f_text = t.get_bytes('f')
223
f_sha = osutils.sha_string(f_text)
225
null_stat = dirstate.DirState.NULLSTAT
227
'':(('', '', 'TREE_ROOT'), [
228
('d', '', 0, False, null_stat),
229
('d', '', 0, False, revision_id),
231
'a':(('', 'a', 'a-id'), [
232
('f', '', 0, False, null_stat),
233
('f', a_sha, a_len, False, revision_id),
235
'b':(('', 'b', 'b-id'), [
236
('d', '', 0, False, null_stat),
237
('d', '', 0, False, revision_id),
239
'b/c':(('b', 'c', 'c-id'), [
240
('f', '', 0, False, null_stat),
241
('f', c_sha, c_len, False, revision_id),
243
'b/d':(('b', 'd', 'd-id'), [
244
('d', '', 0, False, null_stat),
245
('d', '', 0, False, revision_id),
247
'b/d/e':(('b/d', 'e', 'e-id'), [
248
('f', '', 0, False, null_stat),
249
('f', e_sha, e_len, False, revision_id),
251
'b-c':(('', 'b-c', 'b-c-id'), [
252
('f', '', 0, False, null_stat),
253
('f', b_c_sha, b_c_len, False, revision_id),
255
'f':(('', 'f', 'f-id'), [
256
('f', '', 0, False, null_stat),
257
('f', f_sha, f_len, False, revision_id),
260
state = dirstate.DirState.from_tree(tree, 'dirstate')
265
# Use a different object, to make sure nothing is pre-cached in memory.
266
state = dirstate.DirState.on_file('dirstate')
268
self.addCleanup(state.unlock)
269
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
270
state._dirblock_state)
271
# This is code is only really tested if we actually have to make more
272
# than one read, so set the page size to something smaller.
273
# We want it to contain about 2.2 records, so that we have a couple
274
# records that we can read per attempt
275
state._bisect_page_size = 200
276
return tree, state, expected
278
def create_duplicated_dirstate(self):
279
"""Create a dirstate with a deleted and added entries.
281
This grabs a basic_dirstate, and then removes and re adds every entry
284
tree, state, expected = self.create_basic_dirstate()
285
# Now we will just remove and add every file so we get an extra entry
286
# per entry. Unversion in reverse order so we handle subdirs
287
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
288
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
289
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
291
# Update the expected dictionary.
292
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
293
orig = expected[path]
295
# This record was deleted in the current tree
296
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
298
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
299
# And didn't exist in the basis tree
300
expected[path2] = (new_key, [orig[1][0],
301
dirstate.DirState.NULL_PARENT_DETAILS])
303
# We will replace the 'dirstate' file underneath 'state', but that is
304
# okay as lock as we unlock 'state' first.
307
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
313
# But we need to leave state in a read-lock because we already have
314
# a cleanup scheduled
316
return tree, state, expected
318
def create_renamed_dirstate(self):
319
"""Create a dirstate with a few internal renames.
321
This takes the basic dirstate, and moves the paths around.
323
tree, state, expected = self.create_basic_dirstate()
325
tree.rename_one('a', 'b/g')
327
tree.rename_one('b/d', 'h')
329
old_a = expected['a']
330
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
331
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
332
('r', 'a', 0, False, '')])
333
old_d = expected['b/d']
334
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
335
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
336
('r', 'b/d', 0, False, '')])
338
old_e = expected['b/d/e']
339
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
341
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
342
('r', 'b/d/e', 0, False, '')])
346
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
353
return tree, state, expected
356
class TestTreeToDirState(TestCaseWithDirState):
358
def test_empty_to_dirstate(self):
359
"""We should be able to create a dirstate for an empty tree."""
360
# There are no files on disk and no parents
361
tree = self.make_branch_and_tree('tree')
362
expected_result = ([], [
363
(('', '', tree.path2id('')), # common details
364
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
366
state = dirstate.DirState.from_tree(tree, 'dirstate')
368
self.check_state_with_reopen(expected_result, state)
370
def test_1_parents_empty_to_dirstate(self):
371
# create a parent by doing a commit
372
tree = self.make_branch_and_tree('tree')
373
rev_id = tree.commit('first post').encode('utf8')
374
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
375
expected_result = ([rev_id], [
376
(('', '', tree.path2id('')), # common details
377
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
378
('d', '', 0, False, rev_id), # first parent details
380
state = dirstate.DirState.from_tree(tree, 'dirstate')
381
self.check_state_with_reopen(expected_result, state)
388
def test_2_parents_empty_to_dirstate(self):
389
# create a parent by doing a commit
390
tree = self.make_branch_and_tree('tree')
391
rev_id = tree.commit('first post')
392
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
393
rev_id2 = tree2.commit('second post', allow_pointless=True)
394
tree.merge_from_branch(tree2.branch)
395
expected_result = ([rev_id, rev_id2], [
396
(('', '', tree.path2id('')), # common details
397
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
398
('d', '', 0, False, rev_id), # first parent details
399
('d', '', 0, False, rev_id2), # second parent details
401
state = dirstate.DirState.from_tree(tree, 'dirstate')
402
self.check_state_with_reopen(expected_result, state)
409
def test_empty_unknowns_are_ignored_to_dirstate(self):
410
"""We should be able to create a dirstate for an empty tree."""
411
# There are no files on disk and no parents
412
tree = self.make_branch_and_tree('tree')
413
self.build_tree(['tree/unknown'])
414
expected_result = ([], [
415
(('', '', tree.path2id('')), # common details
416
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
418
state = dirstate.DirState.from_tree(tree, 'dirstate')
419
self.check_state_with_reopen(expected_result, state)
421
def get_tree_with_a_file(self):
422
tree = self.make_branch_and_tree('tree')
423
self.build_tree(['tree/a file'])
424
tree.add('a file', 'a file id')
427
def test_non_empty_no_parents_to_dirstate(self):
428
"""We should be able to create a dirstate for an empty tree."""
429
# There are files on disk and no parents
430
tree = self.get_tree_with_a_file()
431
expected_result = ([], [
432
(('', '', tree.path2id('')), # common details
433
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
435
(('', 'a file', 'a file id'), # common
436
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
439
state = dirstate.DirState.from_tree(tree, 'dirstate')
440
self.check_state_with_reopen(expected_result, state)
442
def test_1_parents_not_empty_to_dirstate(self):
443
# create a parent by doing a commit
444
tree = self.get_tree_with_a_file()
445
rev_id = tree.commit('first post').encode('utf8')
446
# change the current content to be different this will alter stat, sha
448
self.build_tree_contents([('tree/a file', 'new content\n')])
449
expected_result = ([rev_id], [
450
(('', '', tree.path2id('')), # common details
451
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
452
('d', '', 0, False, rev_id), # first parent details
454
(('', 'a file', 'a file id'), # common
455
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
456
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
457
rev_id), # first parent
460
state = dirstate.DirState.from_tree(tree, 'dirstate')
461
self.check_state_with_reopen(expected_result, state)
463
def test_2_parents_not_empty_to_dirstate(self):
464
# create a parent by doing a commit
465
tree = self.get_tree_with_a_file()
466
rev_id = tree.commit('first post').encode('utf8')
467
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
468
# change the current content to be different this will alter stat, sha
470
self.build_tree_contents([('tree2/a file', 'merge content\n')])
471
rev_id2 = tree2.commit('second post').encode('utf8')
472
tree.merge_from_branch(tree2.branch)
473
# change the current content to be different this will alter stat, sha
474
# and length again, giving us three distinct values:
475
self.build_tree_contents([('tree/a file', 'new content\n')])
476
expected_result = ([rev_id, rev_id2], [
477
(('', '', tree.path2id('')), # common details
478
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
479
('d', '', 0, False, rev_id), # first parent details
480
('d', '', 0, False, rev_id2), # second parent details
482
(('', 'a file', 'a file id'), # common
483
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
484
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
485
rev_id), # first parent
486
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
487
rev_id2), # second parent
490
state = dirstate.DirState.from_tree(tree, 'dirstate')
491
self.check_state_with_reopen(expected_result, state)
493
def test_colliding_fileids(self):
494
# test insertion of parents creating several entries at the same path.
495
# we used to have a bug where they could cause the dirstate to break
496
# its ordering invariants.
497
# create some trees to test from
500
tree = self.make_branch_and_tree('tree%d' % i)
501
self.build_tree(['tree%d/name' % i,])
502
tree.add(['name'], ['file-id%d' % i])
503
revision_id = 'revid-%d' % i
504
tree.commit('message', rev_id=revision_id)
505
parents.append((revision_id,
506
tree.branch.repository.revision_tree(revision_id)))
507
# now fold these trees into a dirstate
508
state = dirstate.DirState.initialize('dirstate')
510
state.set_parent_trees(parents, [])
516
class TestDirStateOnFile(TestCaseWithDirState):
518
def test_construct_with_path(self):
519
tree = self.make_branch_and_tree('tree')
520
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
521
# we want to be able to get the lines of the dirstate that we will
523
lines = state.get_lines()
525
self.build_tree_contents([('dirstate', ''.join(lines))])
527
# no parents, default tree content
528
expected_result = ([], [
529
(('', '', tree.path2id('')), # common details
530
# current tree details, but new from_tree skips statting, it
531
# uses set_state_from_inventory, and thus depends on the
533
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
536
state = dirstate.DirState.on_file('dirstate')
537
state.lock_write() # check_state_with_reopen will save() and unlock it
538
self.check_state_with_reopen(expected_result, state)
540
def test_can_save_clean_on_file(self):
541
tree = self.make_branch_and_tree('tree')
542
state = dirstate.DirState.from_tree(tree, 'dirstate')
544
# doing a save should work here as there have been no changes.
546
# TODO: stat it and check it hasn't changed; may require waiting
547
# for the state accuracy window.
551
def test_can_save_in_read_lock(self):
552
self.build_tree(['a-file'])
553
state = dirstate.DirState.initialize('dirstate')
555
# No stat and no sha1 sum.
556
state.add('a-file', 'a-file-id', 'file', None, '')
561
# Now open in readonly mode
562
state = dirstate.DirState.on_file('dirstate')
565
entry = state._get_entry(0, path_utf8='a-file')
566
# The current sha1 sum should be empty
567
self.assertEqual('', entry[1][0][1])
568
# We should have a real entry.
569
self.assertNotEqual((None, None), entry)
570
# Make sure everything is old enough
571
state._sha_cutoff_time()
572
state._cutoff_time += 10
573
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
574
# We should have gotten a real sha1
575
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
578
# The dirblock has been updated
579
self.assertEqual(sha1sum, entry[1][0][1])
580
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
581
state._dirblock_state)
584
# Now, since we are the only one holding a lock, we should be able
585
# to save and have it written to disk
590
# Re-open the file, and ensure that the state has been updated.
591
state = dirstate.DirState.on_file('dirstate')
594
entry = state._get_entry(0, path_utf8='a-file')
595
self.assertEqual(sha1sum, entry[1][0][1])
599
def test_save_fails_quietly_if_locked(self):
600
"""If dirstate is locked, save will fail without complaining."""
601
self.build_tree(['a-file'])
602
state = dirstate.DirState.initialize('dirstate')
604
# No stat and no sha1 sum.
605
state.add('a-file', 'a-file-id', 'file', None, '')
610
state = dirstate.DirState.on_file('dirstate')
613
entry = state._get_entry(0, path_utf8='a-file')
614
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
615
# We should have gotten a real sha1
616
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
618
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
619
state._dirblock_state)
621
# Now, before we try to save, grab another dirstate, and take out a
623
# TODO: jam 20070315 Ideally this would be locked by another
624
# process. To make sure the file is really OS locked.
625
state2 = dirstate.DirState.on_file('dirstate')
628
# This won't actually write anything, because it couldn't grab
629
# a write lock. But it shouldn't raise an error, either.
630
# TODO: jam 20070315 We should probably distinguish between
631
# being dirty because of 'update_entry'. And dirty
632
# because of real modification. So that save() *does*
633
# raise a real error if it fails when we have real
641
# The file on disk should not be modified.
642
state = dirstate.DirState.on_file('dirstate')
645
entry = state._get_entry(0, path_utf8='a-file')
646
self.assertEqual('', entry[1][0][1])
651
class TestDirStateInitialize(TestCaseWithDirState):
653
def test_initialize(self):
654
expected_result = ([], [
655
(('', '', 'TREE_ROOT'), # common details
656
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
659
state = dirstate.DirState.initialize('dirstate')
661
self.assertIsInstance(state, dirstate.DirState)
662
lines = state.get_lines()
665
# On win32 you can't read from a locked file, even within the same
666
# process. So we have to unlock and release before we check the file
668
self.assertFileEqual(''.join(lines), 'dirstate')
669
state.lock_read() # check_state_with_reopen will unlock
670
self.check_state_with_reopen(expected_result, state)
673
class TestDirStateManipulations(TestCaseWithDirState):
675
def test_set_state_from_inventory_no_content_no_parents(self):
676
# setting the current inventory is a slow but important api to support.
677
tree1 = self.make_branch_and_memory_tree('tree1')
681
revid1 = tree1.commit('foo').encode('utf8')
682
root_id = tree1.inventory.root.file_id
683
inv = tree1.inventory
686
expected_result = [], [
687
(('', '', root_id), [
688
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
689
state = dirstate.DirState.initialize('dirstate')
691
state.set_state_from_inventory(inv)
692
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
694
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
695
state._dirblock_state)
700
# This will unlock it
701
self.check_state_with_reopen(expected_result, state)
703
def test_set_state_from_inventory_preserves_hashcache(self):
704
# https://bugs.launchpad.net/bzr/+bug/146176
705
# set_state_from_inventory should preserve the stat and hash value for
706
# workingtree files that are not changed by the inventory.
708
tree = self.make_branch_and_tree('.')
709
# depends on the default format using dirstate...
712
# make a dirstate with some valid hashcache data
713
# file on disk, but that's not needed for this test
714
foo_contents = 'contents of foo'
715
self.build_tree_contents([('foo', foo_contents)])
716
tree.add('foo', 'foo-id')
718
foo_stat = os.stat('foo')
719
foo_packed = dirstate.pack_stat(foo_stat)
720
foo_sha = osutils.sha_string(foo_contents)
721
foo_size = len(foo_contents)
723
# should not be cached yet, because the file's too fresh
725
(('', 'foo', 'foo-id',),
726
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
727
tree._dirstate._get_entry(0, 'foo-id'))
728
# poke in some hashcache information - it wouldn't normally be
729
# stored because it's too fresh
730
tree._dirstate.update_minimal(
731
('', 'foo', 'foo-id'),
732
'f', False, foo_sha, foo_packed, foo_size, 'foo')
733
# now should be cached
735
(('', 'foo', 'foo-id',),
736
[('f', foo_sha, foo_size, False, foo_packed)]),
737
tree._dirstate._get_entry(0, 'foo-id'))
739
# extract the inventory, and add something to it
740
inv = tree._get_inventory()
741
# should see the file we poked in...
742
self.assertTrue(inv.has_id('foo-id'))
743
self.assertTrue(inv.has_filename('foo'))
744
inv.add_path('bar', 'file', 'bar-id')
745
tree._dirstate._validate()
746
# this used to cause it to lose its hashcache
747
tree._dirstate.set_state_from_inventory(inv)
748
tree._dirstate._validate()
754
# now check that the state still has the original hashcache value
755
state = tree._dirstate
757
foo_tuple = state._get_entry(0, path_utf8='foo')
759
(('', 'foo', 'foo-id',),
760
[('f', foo_sha, len(foo_contents), False,
761
dirstate.pack_stat(foo_stat))]),
767
def test_set_state_from_inventory_mixed_paths(self):
768
tree1 = self.make_branch_and_tree('tree1')
769
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
770
'tree1/a/b/foo', 'tree1/a-b/bar'])
773
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
774
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
775
tree1.commit('rev1', rev_id='rev1')
776
root_id = tree1.get_root_id()
777
inv = tree1.inventory
780
expected_result1 = [('', '', root_id, 'd'),
781
('', 'a', 'a-id', 'd'),
782
('', 'a-b', 'a-b-id', 'd'),
783
('a', 'b', 'b-id', 'd'),
784
('a/b', 'foo', 'foo-id', 'f'),
785
('a-b', 'bar', 'bar-id', 'f'),
787
expected_result2 = [('', '', root_id, 'd'),
788
('', 'a', 'a-id', 'd'),
789
('', 'a-b', 'a-b-id', 'd'),
790
('a-b', 'bar', 'bar-id', 'f'),
792
state = dirstate.DirState.initialize('dirstate')
794
state.set_state_from_inventory(inv)
796
for entry in state._iter_entries():
797
values.append(entry[0] + entry[1][0][:1])
798
self.assertEqual(expected_result1, values)
800
state.set_state_from_inventory(inv)
802
for entry in state._iter_entries():
803
values.append(entry[0] + entry[1][0][:1])
804
self.assertEqual(expected_result2, values)
808
def test_set_path_id_no_parents(self):
809
"""The id of a path can be changed trivally with no parents."""
810
state = dirstate.DirState.initialize('dirstate')
812
# check precondition to be sure the state does change appropriately.
814
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
815
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
816
list(state._iter_entries()))
817
state.set_path_id('', 'foobarbaz')
819
(('', '', 'foobarbaz'), [('d', '', 0, False,
820
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
821
self.assertEqual(expected_rows, list(state._iter_entries()))
822
# should work across save too
826
state = dirstate.DirState.on_file('dirstate')
830
self.assertEqual(expected_rows, list(state._iter_entries()))
834
def test_set_path_id_with_parents(self):
835
"""Set the root file id in a dirstate with parents"""
836
mt = self.make_branch_and_tree('mt')
837
# in case the default tree format uses a different root id
838
mt.set_root_id('TREE_ROOT')
839
mt.commit('foo', rev_id='parent-revid')
840
rt = mt.branch.repository.revision_tree('parent-revid')
841
state = dirstate.DirState.initialize('dirstate')
844
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
845
state.set_path_id('', 'foobarbaz')
847
# now see that it is what we expected
849
(('', '', 'TREE_ROOT'),
850
[('a', '', 0, False, ''),
851
('d', '', 0, False, 'parent-revid'),
853
(('', '', 'foobarbaz'),
854
[('d', '', 0, False, ''),
855
('a', '', 0, False, ''),
859
self.assertEqual(expected_rows, list(state._iter_entries()))
860
# should work across save too
864
# now flush & check we get the same
865
state = dirstate.DirState.on_file('dirstate')
869
self.assertEqual(expected_rows, list(state._iter_entries()))
872
# now change within an existing file-backed state
876
state.set_path_id('', 'tree-root-2')
882
def test_set_parent_trees_no_content(self):
883
# set_parent_trees is a slow but important api to support.
884
tree1 = self.make_branch_and_memory_tree('tree1')
888
revid1 = tree1.commit('foo')
891
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
892
tree2 = MemoryTree.create_on_branch(branch2)
895
revid2 = tree2.commit('foo')
896
root_id = tree2.inventory.root.file_id
899
state = dirstate.DirState.initialize('dirstate')
901
state.set_path_id('', root_id)
902
state.set_parent_trees(
903
((revid1, tree1.branch.repository.revision_tree(revid1)),
904
(revid2, tree2.branch.repository.revision_tree(revid2)),
905
('ghost-rev', None)),
907
# check we can reopen and use the dirstate after setting parent
914
state = dirstate.DirState.on_file('dirstate')
917
self.assertEqual([revid1, revid2, 'ghost-rev'],
918
state.get_parent_ids())
919
# iterating the entire state ensures that the state is parsable.
920
list(state._iter_entries())
921
# be sure that it sets not appends - change it
922
state.set_parent_trees(
923
((revid1, tree1.branch.repository.revision_tree(revid1)),
924
('ghost-rev', None)),
926
# and now put it back.
927
state.set_parent_trees(
928
((revid1, tree1.branch.repository.revision_tree(revid1)),
929
(revid2, tree2.branch.repository.revision_tree(revid2)),
930
('ghost-rev', tree2.branch.repository.revision_tree(None))),
932
self.assertEqual([revid1, revid2, 'ghost-rev'],
933
state.get_parent_ids())
934
# the ghost should be recorded as such by set_parent_trees.
935
self.assertEqual(['ghost-rev'], state.get_ghosts())
937
[(('', '', root_id), [
938
('d', '', 0, False, dirstate.DirState.NULLSTAT),
939
('d', '', 0, False, revid1),
940
('d', '', 0, False, revid2)
942
list(state._iter_entries()))
946
def test_set_parent_trees_file_missing_from_tree(self):
947
# Adding a parent tree may reference files not in the current state.
948
# they should get listed just once by id, even if they are in two
950
# set_parent_trees is a slow but important api to support.
951
tree1 = self.make_branch_and_memory_tree('tree1')
955
tree1.add(['a file'], ['file-id'], ['file'])
956
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
957
revid1 = tree1.commit('foo')
960
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
961
tree2 = MemoryTree.create_on_branch(branch2)
964
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
965
revid2 = tree2.commit('foo')
966
root_id = tree2.inventory.root.file_id
969
# check the layout in memory
970
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
971
(('', '', root_id), [
972
('d', '', 0, False, dirstate.DirState.NULLSTAT),
973
('d', '', 0, False, revid1.encode('utf8')),
974
('d', '', 0, False, revid2.encode('utf8'))
976
(('', 'a file', 'file-id'), [
977
('a', '', 0, False, ''),
978
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
979
revid1.encode('utf8')),
980
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
981
revid2.encode('utf8'))
984
state = dirstate.DirState.initialize('dirstate')
986
state.set_path_id('', root_id)
987
state.set_parent_trees(
988
((revid1, tree1.branch.repository.revision_tree(revid1)),
989
(revid2, tree2.branch.repository.revision_tree(revid2)),
995
# check_state_with_reopen will unlock
996
self.check_state_with_reopen(expected_result, state)
998
### add a path via _set_data - so we dont need delta work, just
999
# raw data in, and ensure that it comes out via get_lines happily.
1001
def test_add_path_to_root_no_parents_all_data(self):
1002
# The most trivial addition of a path is when there are no parents and
1003
# its in the root and all data about the file is supplied
1004
self.build_tree(['a file'])
1005
stat = os.lstat('a file')
1006
# the 1*20 is the sha1 pretend value.
1007
state = dirstate.DirState.initialize('dirstate')
1008
expected_entries = [
1009
(('', '', 'TREE_ROOT'), [
1010
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1012
(('', 'a file', 'a file id'), [
1013
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1017
state.add('a file', 'a file id', 'file', stat, '1'*20)
1018
# having added it, it should be in the output of iter_entries.
1019
self.assertEqual(expected_entries, list(state._iter_entries()))
1020
# saving and reloading should not affect this.
1024
state = dirstate.DirState.on_file('dirstate')
1027
self.assertEqual(expected_entries, list(state._iter_entries()))
1031
def test_add_path_to_unversioned_directory(self):
1032
"""Adding a path to an unversioned directory should error.
1034
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1035
once dirstate is stable and if it is merged with WorkingTree3, consider
1036
removing this copy of the test.
1038
self.build_tree(['unversioned/', 'unversioned/a file'])
1039
state = dirstate.DirState.initialize('dirstate')
1041
self.assertRaises(errors.NotVersionedError, state.add,
1042
'unversioned/a file', 'a file id', 'file', None, None)
1046
def test_add_directory_to_root_no_parents_all_data(self):
1047
# The most trivial addition of a dir is when there are no parents and
1048
# its in the root and all data about the file is supplied
1049
self.build_tree(['a dir/'])
1050
stat = os.lstat('a dir')
1051
expected_entries = [
1052
(('', '', 'TREE_ROOT'), [
1053
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1055
(('', 'a dir', 'a dir id'), [
1056
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1059
state = dirstate.DirState.initialize('dirstate')
1061
state.add('a dir', 'a dir id', 'directory', stat, None)
1062
# having added it, it should be in the output of iter_entries.
1063
self.assertEqual(expected_entries, list(state._iter_entries()))
1064
# saving and reloading should not affect this.
1068
state = dirstate.DirState.on_file('dirstate')
1072
self.assertEqual(expected_entries, list(state._iter_entries()))
1076
def test_add_symlink_to_root_no_parents_all_data(self):
1077
# The most trivial addition of a symlink when there are no parents and
1078
# its in the root and all data about the file is supplied
1079
# bzr doesn't support fake symlinks on windows, yet.
1080
if not has_symlinks():
1081
raise TestSkipped("No symlink support")
1082
os.symlink('target', 'a link')
1083
stat = os.lstat('a link')
1084
expected_entries = [
1085
(('', '', 'TREE_ROOT'), [
1086
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1088
(('', 'a link', 'a link id'), [
1089
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1092
state = dirstate.DirState.initialize('dirstate')
1094
state.add('a link', 'a link id', 'symlink', stat, 'target')
1095
# having added it, it should be in the output of iter_entries.
1096
self.assertEqual(expected_entries, list(state._iter_entries()))
1097
# saving and reloading should not affect this.
1101
state = dirstate.DirState.on_file('dirstate')
1104
self.assertEqual(expected_entries, list(state._iter_entries()))
1108
def test_add_directory_and_child_no_parents_all_data(self):
1109
# after adding a directory, we should be able to add children to it.
1110
self.build_tree(['a dir/', 'a dir/a file'])
1111
dirstat = os.lstat('a dir')
1112
filestat = os.lstat('a dir/a file')
1113
expected_entries = [
1114
(('', '', 'TREE_ROOT'), [
1115
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1117
(('', 'a dir', 'a dir id'), [
1118
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1120
(('a dir', 'a file', 'a file id'), [
1121
('f', '1'*20, 25, False,
1122
dirstate.pack_stat(filestat)), # current tree details
1125
state = dirstate.DirState.initialize('dirstate')
1127
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1128
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
1129
# added it, it should be in the output of iter_entries.
1130
self.assertEqual(expected_entries, list(state._iter_entries()))
1131
# saving and reloading should not affect this.
1135
state = dirstate.DirState.on_file('dirstate')
1138
self.assertEqual(expected_entries, list(state._iter_entries()))
1142
def test_add_tree_reference(self):
1143
# make a dirstate and add a tree reference
1144
state = dirstate.DirState.initialize('dirstate')
1146
('', 'subdir', 'subdir-id'),
1147
[('t', 'subtree-123123', 0, False,
1148
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1151
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1152
entry = state._get_entry(0, 'subdir-id', 'subdir')
1153
self.assertEqual(entry, expected_entry)
1158
# now check we can read it back
1162
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1163
self.assertEqual(entry, entry2)
1164
self.assertEqual(entry, expected_entry)
1165
# and lookup by id should work too
1166
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1167
self.assertEqual(entry, expected_entry)
1171
def test_add_forbidden_names(self):
1172
state = dirstate.DirState.initialize('dirstate')
1173
self.addCleanup(state.unlock)
1174
self.assertRaises(errors.BzrError,
1175
state.add, '.', 'ass-id', 'directory', None, None)
1176
self.assertRaises(errors.BzrError,
1177
state.add, '..', 'ass-id', 'directory', None, None)
1180
class TestGetLines(TestCaseWithDirState):
1182
def test_get_line_with_2_rows(self):
1183
state = self.create_dirstate_with_root_and_subdir()
1185
self.assertEqual(['#bazaar dirstate flat format 3\n',
1186
'crc32: 41262208\n',
1190
'\x00\x00a-root-value\x00'
1191
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1192
'\x00subdir\x00subdir-id\x00'
1193
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1194
], state.get_lines())
1198
def test_entry_to_line(self):
1199
state = self.create_dirstate_with_root()
1202
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1203
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1204
state._entry_to_line(state._dirblocks[0][1][0]))
1208
def test_entry_to_line_with_parent(self):
1209
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1210
root_entry = ('', '', 'a-root-value'), [
1211
('d', '', 0, False, packed_stat), # current tree details
1212
# first: a pointer to the current location
1213
('a', 'dirname/basename', 0, False, ''),
1215
state = dirstate.DirState.initialize('dirstate')
1218
'\x00\x00a-root-value\x00'
1219
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1220
'a\x00dirname/basename\x000\x00n\x00',
1221
state._entry_to_line(root_entry))
1225
def test_entry_to_line_with_two_parents_at_different_paths(self):
1226
# / in the tree, at / in one parent and /dirname/basename in the other.
1227
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1228
root_entry = ('', '', 'a-root-value'), [
1229
('d', '', 0, False, packed_stat), # current tree details
1230
('d', '', 0, False, 'rev_id'), # first parent details
1231
# second: a pointer to the current location
1232
('a', 'dirname/basename', 0, False, ''),
1234
state = dirstate.DirState.initialize('dirstate')
1237
'\x00\x00a-root-value\x00'
1238
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1239
'd\x00\x000\x00n\x00rev_id\x00'
1240
'a\x00dirname/basename\x000\x00n\x00',
1241
state._entry_to_line(root_entry))
1245
def test_iter_entries(self):
1246
# we should be able to iterate the dirstate entries from end to end
1247
# this is for get_lines to be easy to read.
1248
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1250
root_entries = [(('', '', 'a-root-value'), [
1251
('d', '', 0, False, packed_stat), # current tree details
1253
dirblocks.append(('', root_entries))
1254
# add two files in the root
1255
subdir_entry = ('', 'subdir', 'subdir-id'), [
1256
('d', '', 0, False, packed_stat), # current tree details
1258
afile_entry = ('', 'afile', 'afile-id'), [
1259
('f', 'sha1value', 34, False, packed_stat), # current tree details
1261
dirblocks.append(('', [subdir_entry, afile_entry]))
1263
file_entry2 = ('subdir', '2file', '2file-id'), [
1264
('f', 'sha1value', 23, False, packed_stat), # current tree details
1266
dirblocks.append(('subdir', [file_entry2]))
1267
state = dirstate.DirState.initialize('dirstate')
1269
state._set_data([], dirblocks)
1270
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1272
self.assertEqual(expected_entries, list(state._iter_entries()))
1277
class TestGetBlockRowIndex(TestCaseWithDirState):
1279
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1280
file_present, state, dirname, basename, tree_index):
1281
self.assertEqual((block_index, row_index, dir_present, file_present),
1282
state._get_block_entry_index(dirname, basename, tree_index))
1284
block = state._dirblocks[block_index]
1285
self.assertEqual(dirname, block[0])
1286
if dir_present and file_present:
1287
row = state._dirblocks[block_index][1][row_index]
1288
self.assertEqual(dirname, row[0][0])
1289
self.assertEqual(basename, row[0][1])
1291
def test_simple_structure(self):
1292
state = self.create_dirstate_with_root_and_subdir()
1293
self.addCleanup(state.unlock)
1294
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1295
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1296
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1297
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1298
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1301
def test_complex_structure_exists(self):
1302
state = self.create_complex_dirstate()
1303
self.addCleanup(state.unlock)
1304
# Make sure we can find everything that exists
1305
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1306
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1307
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1308
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1309
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1310
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1311
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1312
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1313
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1314
'b', 'h\xc3\xa5', 0)
1316
def test_complex_structure_missing(self):
1317
state = self.create_complex_dirstate()
1318
self.addCleanup(state.unlock)
1319
# Make sure things would be inserted in the right locations
1320
# '_' comes before 'a'
1321
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1322
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1323
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1324
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1326
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1327
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1328
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1329
# This would be inserted between a/ and b/
1330
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1332
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1335
class TestGetEntry(TestCaseWithDirState):
1337
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1338
"""Check that the right entry is returned for a request to getEntry."""
1339
entry = state._get_entry(index, path_utf8=path)
1341
self.assertEqual((None, None), entry)
1344
self.assertEqual((dirname, basename, file_id), cur[:3])
1346
def test_simple_structure(self):
1347
state = self.create_dirstate_with_root_and_subdir()
1348
self.addCleanup(state.unlock)
1349
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1350
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1351
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1352
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1353
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1355
def test_complex_structure_exists(self):
1356
state = self.create_complex_dirstate()
1357
self.addCleanup(state.unlock)
1358
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1359
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1360
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1361
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1362
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1363
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1364
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1365
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1366
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1369
def test_complex_structure_missing(self):
1370
state = self.create_complex_dirstate()
1371
self.addCleanup(state.unlock)
1372
self.assertEntryEqual(None, None, None, state, '_', 0)
1373
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1374
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1375
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1377
def test_get_entry_uninitialized(self):
1378
"""Calling get_entry will load data if it needs to"""
1379
state = self.create_dirstate_with_root()
1385
state = dirstate.DirState.on_file('dirstate')
1388
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1389
state._header_state)
1390
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1391
state._dirblock_state)
1392
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1397
class TestDirstateSortOrder(TestCaseWithTransport):
1398
"""Test that DirState adds entries in the right order."""
1400
def test_add_sorting(self):
1401
"""Add entries in lexicographical order, we get path sorted order.
1403
This tests it to a depth of 4, to make sure we don't just get it right
1404
at a single depth. 'a/a' should come before 'a-a', even though it
1405
doesn't lexicographically.
1407
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1408
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1411
state = dirstate.DirState.initialize('dirstate')
1412
self.addCleanup(state.unlock)
1414
fake_stat = os.stat('dirstate')
1416
d_id = d.replace('/', '_')+'-id'
1417
file_path = d + '/f'
1418
file_id = file_path.replace('/', '_')+'-id'
1419
state.add(d, d_id, 'directory', fake_stat, null_sha)
1420
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1422
expected = ['', '', 'a',
1423
'a/a', 'a/a/a', 'a/a/a/a',
1424
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1426
split = lambda p:p.split('/')
1427
self.assertEqual(sorted(expected, key=split), expected)
1428
dirblock_names = [d[0] for d in state._dirblocks]
1429
self.assertEqual(expected, dirblock_names)
1431
def test_set_parent_trees_correct_order(self):
1432
"""After calling set_parent_trees() we should maintain the order."""
1433
dirs = ['a', 'a-a', 'a/a']
1435
state = dirstate.DirState.initialize('dirstate')
1436
self.addCleanup(state.unlock)
1438
fake_stat = os.stat('dirstate')
1440
d_id = d.replace('/', '_')+'-id'
1441
file_path = d + '/f'
1442
file_id = file_path.replace('/', '_')+'-id'
1443
state.add(d, d_id, 'directory', fake_stat, null_sha)
1444
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1446
expected = ['', '', 'a', 'a/a', 'a-a']
1447
dirblock_names = [d[0] for d in state._dirblocks]
1448
self.assertEqual(expected, dirblock_names)
1450
# *really* cheesy way to just get an empty tree
1451
repo = self.make_repository('repo')
1452
empty_tree = repo.revision_tree(None)
1453
state.set_parent_trees([('null:', empty_tree)], [])
1455
dirblock_names = [d[0] for d in state._dirblocks]
1456
self.assertEqual(expected, dirblock_names)
1459
class InstrumentedDirState(dirstate.DirState):
1460
"""An DirState with instrumented sha1 functionality."""
1462
def __init__(self, path):
1463
super(InstrumentedDirState, self).__init__(path)
1464
self._time_offset = 0
1466
# member is dynamically set in DirState.__init__ to turn on trace
1467
self._sha1_file = self._sha1_file_and_log
1469
def _sha_cutoff_time(self):
1470
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1471
self._cutoff_time = timestamp + self._time_offset
1473
def _sha1_file_and_log(self, abspath):
1474
self._log.append(('sha1', abspath))
1475
return osutils.sha_file_by_name(abspath)
1477
def _read_link(self, abspath, old_link):
1478
self._log.append(('read_link', abspath, old_link))
1479
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1481
def _lstat(self, abspath, entry):
1482
self._log.append(('lstat', abspath))
1483
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1485
def _is_executable(self, mode, old_executable):
1486
self._log.append(('is_exec', mode, old_executable))
1487
return super(InstrumentedDirState, self)._is_executable(mode,
1490
def adjust_time(self, secs):
1491
"""Move the clock forward or back.
1493
:param secs: The amount to adjust the clock by. Positive values make it
1494
seem as if we are in the future, negative values make it seem like we
1497
self._time_offset += secs
1498
self._cutoff_time = None
1501
class _FakeStat(object):
1502
"""A class with the same attributes as a real stat result."""
1504
def __init__(self, size, mtime, ctime, dev, ino, mode):
1506
self.st_mtime = mtime
1507
self.st_ctime = ctime
1513
class TestUpdateEntry(TestCaseWithDirState):
1514
"""Test the DirState.update_entry functions"""
1516
def get_state_with_a(self):
1517
"""Create a DirState tracking a single object named 'a'"""
1518
state = InstrumentedDirState.initialize('dirstate')
1519
self.addCleanup(state.unlock)
1520
state.add('a', 'a-id', 'file', None, '')
1521
entry = state._get_entry(0, path_utf8='a')
1524
def test_update_entry(self):
1525
state, entry = self.get_state_with_a()
1526
self.build_tree(['a'])
1527
# Add one where we don't provide the stat or sha already
1528
self.assertEqual(('', 'a', 'a-id'), entry[0])
1529
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1531
# Flush the buffers to disk
1533
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1534
state._dirblock_state)
1536
stat_value = os.lstat('a')
1537
packed_stat = dirstate.pack_stat(stat_value)
1538
link_or_sha1 = state.update_entry(entry, abspath='a',
1539
stat_value=stat_value)
1540
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1543
# The dirblock entry should not cache the file's sha1
1544
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1546
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1547
state._dirblock_state)
1548
mode = stat_value.st_mode
1549
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1552
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1553
state._dirblock_state)
1555
# If we do it again right away, we don't know if the file has changed
1556
# so we will re-read the file. Roll the clock back so the file is
1557
# guaranteed to look too new.
1558
state.adjust_time(-10)
1560
link_or_sha1 = state.update_entry(entry, abspath='a',
1561
stat_value=stat_value)
1562
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1563
('sha1', 'a'), ('is_exec', mode, False),
1565
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1567
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1568
state._dirblock_state)
1569
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1573
# However, if we move the clock forward so the file is considered
1574
# "stable", it should just cache the value.
1575
state.adjust_time(+20)
1576
link_or_sha1 = state.update_entry(entry, abspath='a',
1577
stat_value=stat_value)
1578
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1580
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1581
('sha1', 'a'), ('is_exec', mode, False),
1582
('sha1', 'a'), ('is_exec', mode, False),
1584
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1587
# Subsequent calls will just return the cached value
1588
link_or_sha1 = state.update_entry(entry, abspath='a',
1589
stat_value=stat_value)
1590
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1592
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1593
('sha1', 'a'), ('is_exec', mode, False),
1594
('sha1', 'a'), ('is_exec', mode, False),
1596
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1599
def test_update_entry_symlink(self):
1600
"""Update entry should read symlinks."""
1601
if not osutils.has_symlinks():
1602
# PlatformDeficiency / TestSkipped
1603
raise TestSkipped("No symlink support")
1604
state, entry = self.get_state_with_a()
1606
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1607
state._dirblock_state)
1608
os.symlink('target', 'a')
1610
state.adjust_time(-10) # Make the symlink look new
1611
stat_value = os.lstat('a')
1612
packed_stat = dirstate.pack_stat(stat_value)
1613
link_or_sha1 = state.update_entry(entry, abspath='a',
1614
stat_value=stat_value)
1615
self.assertEqual('target', link_or_sha1)
1616
self.assertEqual([('read_link', 'a', '')], state._log)
1617
# Dirblock is not updated (the link is too new)
1618
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1620
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1621
state._dirblock_state)
1623
# Because the stat_value looks new, we should re-read the target
1624
link_or_sha1 = state.update_entry(entry, abspath='a',
1625
stat_value=stat_value)
1626
self.assertEqual('target', link_or_sha1)
1627
self.assertEqual([('read_link', 'a', ''),
1628
('read_link', 'a', ''),
1630
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1632
state.adjust_time(+20) # Skip into the future, all files look old
1633
link_or_sha1 = state.update_entry(entry, abspath='a',
1634
stat_value=stat_value)
1635
self.assertEqual('target', link_or_sha1)
1636
# We need to re-read the link because only now can we cache it
1637
self.assertEqual([('read_link', 'a', ''),
1638
('read_link', 'a', ''),
1639
('read_link', 'a', ''),
1641
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1644
# Another call won't re-read the link
1645
self.assertEqual([('read_link', 'a', ''),
1646
('read_link', 'a', ''),
1647
('read_link', 'a', ''),
1649
link_or_sha1 = state.update_entry(entry, abspath='a',
1650
stat_value=stat_value)
1651
self.assertEqual('target', link_or_sha1)
1652
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1655
def do_update_entry(self, state, entry, abspath):
1656
stat_value = os.lstat(abspath)
1657
return state.update_entry(entry, abspath, stat_value)
1659
def test_update_entry_dir(self):
1660
state, entry = self.get_state_with_a()
1661
self.build_tree(['a/'])
1662
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1664
def test_update_entry_dir_unchanged(self):
1665
state, entry = self.get_state_with_a()
1666
self.build_tree(['a/'])
1667
state.adjust_time(+20)
1668
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1669
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1670
state._dirblock_state)
1672
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1673
state._dirblock_state)
1674
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1675
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1676
state._dirblock_state)
1678
def test_update_entry_file_unchanged(self):
1679
state, entry = self.get_state_with_a()
1680
self.build_tree(['a'])
1681
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1682
state.adjust_time(+20)
1683
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1684
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1685
state._dirblock_state)
1687
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1688
state._dirblock_state)
1689
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1690
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1691
state._dirblock_state)
1693
def create_and_test_file(self, state, entry):
1694
"""Create a file at 'a' and verify the state finds it.
1696
The state should already be versioning *something* at 'a'. This makes
1697
sure that state.update_entry recognizes it as a file.
1699
self.build_tree(['a'])
1700
stat_value = os.lstat('a')
1701
packed_stat = dirstate.pack_stat(stat_value)
1703
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1704
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1706
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1710
def create_and_test_dir(self, state, entry):
1711
"""Create a directory at 'a' and verify the state finds it.
1713
The state should already be versioning *something* at 'a'. This makes
1714
sure that state.update_entry recognizes it as a directory.
1716
self.build_tree(['a/'])
1717
stat_value = os.lstat('a')
1718
packed_stat = dirstate.pack_stat(stat_value)
1720
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1721
self.assertIs(None, link_or_sha1)
1722
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1726
def create_and_test_symlink(self, state, entry):
1727
"""Create a symlink at 'a' and verify the state finds it.
1729
The state should already be versioning *something* at 'a'. This makes
1730
sure that state.update_entry recognizes it as a symlink.
1732
This should not be called if this platform does not have symlink
1735
# caller should care about skipping test on platforms without symlinks
1736
os.symlink('path/to/foo', 'a')
1738
stat_value = os.lstat('a')
1739
packed_stat = dirstate.pack_stat(stat_value)
1741
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1742
self.assertEqual('path/to/foo', link_or_sha1)
1743
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1747
def test_update_file_to_dir(self):
1748
"""If a file changes to a directory we return None for the sha.
1749
We also update the inventory record.
1751
state, entry = self.get_state_with_a()
1752
# The file sha1 won't be cached unless the file is old
1753
state.adjust_time(+10)
1754
self.create_and_test_file(state, entry)
1756
self.create_and_test_dir(state, entry)
1758
def test_update_file_to_symlink(self):
1759
"""File becomes a symlink"""
1760
if not osutils.has_symlinks():
1761
# PlatformDeficiency / TestSkipped
1762
raise TestSkipped("No symlink support")
1763
state, entry = self.get_state_with_a()
1764
# The file sha1 won't be cached unless the file is old
1765
state.adjust_time(+10)
1766
self.create_and_test_file(state, entry)
1768
self.create_and_test_symlink(state, entry)
1770
def test_update_dir_to_file(self):
1771
"""Directory becoming a file updates the entry."""
1772
state, entry = self.get_state_with_a()
1773
# The file sha1 won't be cached unless the file is old
1774
state.adjust_time(+10)
1775
self.create_and_test_dir(state, entry)
1777
self.create_and_test_file(state, entry)
1779
def test_update_dir_to_symlink(self):
1780
"""Directory becomes a symlink"""
1781
if not osutils.has_symlinks():
1782
# PlatformDeficiency / TestSkipped
1783
raise TestSkipped("No symlink support")
1784
state, entry = self.get_state_with_a()
1785
# The symlink target won't be cached if it isn't old
1786
state.adjust_time(+10)
1787
self.create_and_test_dir(state, entry)
1789
self.create_and_test_symlink(state, entry)
1791
def test_update_symlink_to_file(self):
1792
"""Symlink becomes a file"""
1793
if not has_symlinks():
1794
raise TestSkipped("No symlink support")
1795
state, entry = self.get_state_with_a()
1796
# The symlink and file info won't be cached unless old
1797
state.adjust_time(+10)
1798
self.create_and_test_symlink(state, entry)
1800
self.create_and_test_file(state, entry)
1802
def test_update_symlink_to_dir(self):
1803
"""Symlink becomes a directory"""
1804
if not has_symlinks():
1805
raise TestSkipped("No symlink support")
1806
state, entry = self.get_state_with_a()
1807
# The symlink target won't be cached if it isn't old
1808
state.adjust_time(+10)
1809
self.create_and_test_symlink(state, entry)
1811
self.create_and_test_dir(state, entry)
1813
def test__is_executable_win32(self):
1814
state, entry = self.get_state_with_a()
1815
self.build_tree(['a'])
1817
# Make sure we are using the win32 implementation of _is_executable
1818
state._is_executable = state._is_executable_win32
1820
# The file on disk is not executable, but we are marking it as though
1821
# it is. With _is_executable_win32 we ignore what is on disk.
1822
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1824
stat_value = os.lstat('a')
1825
packed_stat = dirstate.pack_stat(stat_value)
1827
state.adjust_time(-10) # Make sure everything is new
1828
state.update_entry(entry, abspath='a', stat_value=stat_value)
1830
# The row is updated, but the executable bit stays set.
1831
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1834
# Make the disk object look old enough to cache
1835
state.adjust_time(+20)
1836
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1837
state.update_entry(entry, abspath='a', stat_value=stat_value)
1838
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1841
class TestPackStat(TestCaseWithTransport):
1843
def assertPackStat(self, expected, stat_value):
1844
"""Check the packed and serialized form of a stat value."""
1845
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1847
def test_pack_stat_int(self):
1848
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1849
# Make sure that all parameters have an impact on the packed stat.
1850
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1853
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1854
st.st_mtime = 1172758620
1856
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1857
st.st_ctime = 1172758630
1859
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1862
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1863
st.st_ino = 6499540L
1865
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1866
st.st_mode = 0100744
1868
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1870
def test_pack_stat_float(self):
1871
"""On some platforms mtime and ctime are floats.
1873
Make sure we don't get warnings or errors, and that we ignore changes <
1876
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1877
777L, 6499538L, 0100644)
1878
# These should all be the same as the integer counterparts
1879
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1880
st.st_mtime = 1172758620.0
1882
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1883
st.st_ctime = 1172758630.0
1885
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1886
# fractional seconds are discarded, so no change from above
1887
st.st_mtime = 1172758620.453
1888
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1889
st.st_ctime = 1172758630.228
1890
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1893
class TestBisect(TestCaseWithDirState):
1894
"""Test the ability to bisect into the disk format."""
1896
def assertBisect(self, expected_map, map_keys, state, paths):
1897
"""Assert that bisecting for paths returns the right result.
1899
:param expected_map: A map from key => entry value
1900
:param map_keys: The keys to expect for each path
1901
:param state: The DirState object.
1902
:param paths: A list of paths, these will automatically be split into
1903
(dir, name) tuples, and sorted according to how _bisect
1906
result = state._bisect(paths)
1907
# For now, results are just returned in whatever order we read them.
1908
# We could sort by (dir, name, file_id) or something like that, but in
1909
# the end it would still be fairly arbitrary, and we don't want the
1910
# extra overhead if we can avoid it. So sort everything to make sure
1912
assert len(map_keys) == len(paths)
1914
for path, keys in zip(paths, map_keys):
1916
# This should not be present in the output
1918
expected[path] = sorted(expected_map[k] for k in keys)
1920
# The returned values are just arranged randomly based on when they
1921
# were read, for testing, make sure it is properly sorted.
1925
self.assertEqual(expected, result)
1927
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1928
"""Assert that bisecting for dirbblocks returns the right result.
1930
:param expected_map: A map from key => expected values
1931
:param map_keys: A nested list of paths we expect to be returned.
1932
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1933
:param state: The DirState object.
1934
:param paths: A list of directories
1936
result = state._bisect_dirblocks(paths)
1937
assert len(map_keys) == len(paths)
1940
for path, keys in zip(paths, map_keys):
1942
# This should not be present in the output
1944
expected[path] = sorted(expected_map[k] for k in keys)
1948
self.assertEqual(expected, result)
1950
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1951
"""Assert the return value of a recursive bisection.
1953
:param expected_map: A map from key => entry value
1954
:param map_keys: A list of paths we expect to be returned.
1955
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1956
:param state: The DirState object.
1957
:param paths: A list of files and directories. It will be broken up
1958
into (dir, name) pairs and sorted before calling _bisect_recursive.
1961
for key in map_keys:
1962
entry = expected_map[key]
1963
dir_name_id, trees_info = entry
1964
expected[dir_name_id] = trees_info
1966
result = state._bisect_recursive(paths)
1968
self.assertEqual(expected, result)
1970
def test_bisect_each(self):
1971
"""Find a single record using bisect."""
1972
tree, state, expected = self.create_basic_dirstate()
1974
# Bisect should return the rows for the specified files.
1975
self.assertBisect(expected, [['']], state, [''])
1976
self.assertBisect(expected, [['a']], state, ['a'])
1977
self.assertBisect(expected, [['b']], state, ['b'])
1978
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1979
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1980
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1981
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1982
self.assertBisect(expected, [['f']], state, ['f'])
1984
def test_bisect_multi(self):
1985
"""Bisect can be used to find multiple records at the same time."""
1986
tree, state, expected = self.create_basic_dirstate()
1987
# Bisect should be capable of finding multiple entries at the same time
1988
self.assertBisect(expected, [['a'], ['b'], ['f']],
1989
state, ['a', 'b', 'f'])
1990
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1991
state, ['f', 'b/d', 'b/d/e'])
1992
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1993
state, ['b', 'b-c', 'b/c'])
1995
def test_bisect_one_page(self):
1996
"""Test bisect when there is only 1 page to read"""
1997
tree, state, expected = self.create_basic_dirstate()
1998
state._bisect_page_size = 5000
1999
self.assertBisect(expected,[['']], state, [''])
2000
self.assertBisect(expected,[['a']], state, ['a'])
2001
self.assertBisect(expected,[['b']], state, ['b'])
2002
self.assertBisect(expected,[['b/c']], state, ['b/c'])
2003
self.assertBisect(expected,[['b/d']], state, ['b/d'])
2004
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
2005
self.assertBisect(expected,[['b-c']], state, ['b-c'])
2006
self.assertBisect(expected,[['f']], state, ['f'])
2007
self.assertBisect(expected,[['a'], ['b'], ['f']],
2008
state, ['a', 'b', 'f'])
2009
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2010
state, ['b/d', 'b/d/e', 'f'])
2011
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2012
state, ['b', 'b/c', 'b-c'])
2014
def test_bisect_duplicate_paths(self):
2015
"""When bisecting for a path, handle multiple entries."""
2016
tree, state, expected = self.create_duplicated_dirstate()
2018
# Now make sure that both records are properly returned.
2019
self.assertBisect(expected, [['']], state, [''])
2020
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2021
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2022
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2023
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2024
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2026
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2027
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2029
def test_bisect_page_size_too_small(self):
2030
"""If the page size is too small, we will auto increase it."""
2031
tree, state, expected = self.create_basic_dirstate()
2032
state._bisect_page_size = 50
2033
self.assertBisect(expected, [None], state, ['b/e'])
2034
self.assertBisect(expected, [['a']], state, ['a'])
2035
self.assertBisect(expected, [['b']], state, ['b'])
2036
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2037
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2038
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2039
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2040
self.assertBisect(expected, [['f']], state, ['f'])
2042
def test_bisect_missing(self):
2043
"""Test that bisect return None if it cannot find a path."""
2044
tree, state, expected = self.create_basic_dirstate()
2045
self.assertBisect(expected, [None], state, ['foo'])
2046
self.assertBisect(expected, [None], state, ['b/foo'])
2047
self.assertBisect(expected, [None], state, ['bar/foo'])
2048
self.assertBisect(expected, [None], state, ['b-c/foo'])
2050
self.assertBisect(expected, [['a'], None, ['b/d']],
2051
state, ['a', 'foo', 'b/d'])
2053
def test_bisect_rename(self):
2054
"""Check that we find a renamed row."""
2055
tree, state, expected = self.create_renamed_dirstate()
2057
# Search for the pre and post renamed entries
2058
self.assertBisect(expected, [['a']], state, ['a'])
2059
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2060
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2061
self.assertBisect(expected, [['h']], state, ['h'])
2063
# What about b/d/e? shouldn't that also get 2 directory entries?
2064
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2065
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2067
def test_bisect_dirblocks(self):
2068
tree, state, expected = self.create_duplicated_dirstate()
2069
self.assertBisectDirBlocks(expected,
2070
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2072
self.assertBisectDirBlocks(expected,
2073
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2074
self.assertBisectDirBlocks(expected,
2075
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2076
self.assertBisectDirBlocks(expected,
2077
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2078
['b/c', 'b/c2', 'b/d', 'b/d2'],
2079
['b/d/e', 'b/d/e2'],
2080
], state, ['', 'b', 'b/d'])
2082
def test_bisect_dirblocks_missing(self):
2083
tree, state, expected = self.create_basic_dirstate()
2084
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2085
state, ['b/d', 'b/e'])
2086
# Files don't show up in this search
2087
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2088
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2089
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2090
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2091
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2093
def test_bisect_recursive_each(self):
2094
tree, state, expected = self.create_basic_dirstate()
2095
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2096
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2097
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2098
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2099
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2101
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2103
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2107
def test_bisect_recursive_multiple(self):
2108
tree, state, expected = self.create_basic_dirstate()
2109
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2110
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2111
state, ['b/d', 'b/d/e'])
2113
def test_bisect_recursive_missing(self):
2114
tree, state, expected = self.create_basic_dirstate()
2115
self.assertBisectRecursive(expected, [], state, ['d'])
2116
self.assertBisectRecursive(expected, [], state, ['b/e'])
2117
self.assertBisectRecursive(expected, [], state, ['g'])
2118
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2120
def test_bisect_recursive_renamed(self):
2121
tree, state, expected = self.create_renamed_dirstate()
2123
# Looking for either renamed item should find the other
2124
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2125
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2126
# Looking in the containing directory should find the rename target,
2127
# and anything in a subdir of the renamed target.
2128
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2129
'b/d/e', 'b/g', 'h', 'h/e'],
2133
class TestDirstateValidation(TestCaseWithDirState):
2135
def test_validate_correct_dirstate(self):
2136
state = self.create_complex_dirstate()
2139
# and make sure we can also validate with a read lock
2146
def test_dirblock_not_sorted(self):
2147
tree, state, expected = self.create_renamed_dirstate()
2148
state._read_dirblocks_if_needed()
2149
last_dirblock = state._dirblocks[-1]
2150
# we're appending to the dirblock, but this name comes before some of
2151
# the existing names; that's wrong
2152
last_dirblock[1].append(
2153
(('h', 'aaaa', 'a-id'),
2154
[('a', '', 0, False, ''),
2155
('a', '', 0, False, '')]))
2156
e = self.assertRaises(AssertionError,
2158
self.assertContainsRe(str(e), 'not sorted')
2160
def test_dirblock_name_mismatch(self):
2161
tree, state, expected = self.create_renamed_dirstate()
2162
state._read_dirblocks_if_needed()
2163
last_dirblock = state._dirblocks[-1]
2164
# add an entry with the wrong directory name
2165
last_dirblock[1].append(
2167
[('a', '', 0, False, ''),
2168
('a', '', 0, False, '')]))
2169
e = self.assertRaises(AssertionError,
2171
self.assertContainsRe(str(e),
2172
"doesn't match directory name")
2174
def test_dirblock_missing_rename(self):
2175
tree, state, expected = self.create_renamed_dirstate()
2176
state._read_dirblocks_if_needed()
2177
last_dirblock = state._dirblocks[-1]
2178
# make another entry for a-id, without a correct 'r' pointer to
2179
# the real occurrence in the working tree
2180
last_dirblock[1].append(
2181
(('h', 'z', 'a-id'),
2182
[('a', '', 0, False, ''),
2183
('a', '', 0, False, '')]))
2184
e = self.assertRaises(AssertionError,
2186
self.assertContainsRe(str(e),
2187
'file a-id is absent in row')
2190
class TestDirstateTreeReference(TestCaseWithDirState):
2192
def test_reference_revision_is_none(self):
2193
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2194
subtree = self.make_branch_and_tree('tree/subtree',
2195
format='dirstate-with-subtree')
2196
subtree.set_root_id('subtree')
2197
tree.add_reference(subtree)
2199
state = dirstate.DirState.from_tree(tree, 'dirstate')
2200
key = ('', 'subtree', 'subtree')
2201
expected = ('', [(key,
2202
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2205
self.assertEqual(expected, state._find_block(key))