1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
28
from bzrlib.memorytree import MemoryTree
29
from bzrlib.tests import (
32
TestCaseWithTransport,
38
# general checks for NOT_IN_MEMORY error conditions.
39
# set_path_id on a NOT_IN_MEMORY dirstate
40
# set_path_id unicode support
41
# set_path_id setting id of a path not root
42
# set_path_id setting id when there are parents without the id in the parents
43
# set_path_id setting id when there are parents with the id in the parents
44
# set_path_id setting id when state is not in memory
45
# set_path_id setting id when state is in memory unmodified
46
# set_path_id setting id when state is in memory modified
49
class TestCaseWithDirState(TestCaseWithTransport):
50
"""Helper functions for creating DirState objects with various content."""
52
def create_empty_dirstate(self):
53
"""Return a locked but empty dirstate"""
54
state = dirstate.DirState.initialize('dirstate')
57
def create_dirstate_with_root(self):
58
"""Return a write-locked state with a single root entry."""
59
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
60
root_entry_direntry = ('', '', 'a-root-value'), [
61
('d', '', 0, False, packed_stat),
64
dirblocks.append(('', [root_entry_direntry]))
65
dirblocks.append(('', []))
66
state = self.create_empty_dirstate()
68
state._set_data([], dirblocks)
75
def create_dirstate_with_root_and_subdir(self):
76
"""Return a locked DirState with a root and a subdir"""
77
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
78
subdir_entry = ('', 'subdir', 'subdir-id'), [
79
('d', '', 0, False, packed_stat),
81
state = self.create_dirstate_with_root()
83
dirblocks = list(state._dirblocks)
84
dirblocks[1][1].append(subdir_entry)
85
state._set_data([], dirblocks)
91
def create_complex_dirstate(self):
92
"""This dirstate contains multiple files and directories.
102
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
104
Notice that a/e is an empty directory.
106
:return: The dirstate, still write-locked.
108
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
109
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
110
root_entry = ('', '', 'a-root-value'), [
111
('d', '', 0, False, packed_stat),
113
a_entry = ('', 'a', 'a-dir'), [
114
('d', '', 0, False, packed_stat),
116
b_entry = ('', 'b', 'b-dir'), [
117
('d', '', 0, False, packed_stat),
119
c_entry = ('', 'c', 'c-file'), [
120
('f', null_sha, 10, False, packed_stat),
122
d_entry = ('', 'd', 'd-file'), [
123
('f', null_sha, 20, False, packed_stat),
125
e_entry = ('a', 'e', 'e-dir'), [
126
('d', '', 0, False, packed_stat),
128
f_entry = ('a', 'f', 'f-file'), [
129
('f', null_sha, 30, False, packed_stat),
131
g_entry = ('b', 'g', 'g-file'), [
132
('f', null_sha, 30, False, packed_stat),
134
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
135
('f', null_sha, 40, False, packed_stat),
138
dirblocks.append(('', [root_entry]))
139
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
140
dirblocks.append(('a', [e_entry, f_entry]))
141
dirblocks.append(('b', [g_entry, h_entry]))
142
state = dirstate.DirState.initialize('dirstate')
145
state._set_data([], dirblocks)
151
def check_state_with_reopen(self, expected_result, state):
152
"""Check that state has current state expected_result.
154
This will check the current state, open the file anew and check it
156
This function expects the current state to be locked for writing, and
157
will unlock it before re-opening.
158
This is required because we can't open a lock_read() while something
159
else has a lock_write().
160
write => mutually exclusive lock
163
# The state should already be write locked, since we just had to do
164
# some operation to get here.
165
assert state._lock_token is not None
167
self.assertEqual(expected_result[0], state.get_parent_ids())
168
# there should be no ghosts in this tree.
169
self.assertEqual([], state.get_ghosts())
170
# there should be one fileid in this tree - the root of the tree.
171
self.assertEqual(expected_result[1], list(state._iter_entries()))
176
state = dirstate.DirState.on_file('dirstate')
179
self.assertEqual(expected_result[1], list(state._iter_entries()))
183
def create_basic_dirstate(self):
184
"""Create a dirstate with a few files and directories.
194
tree = self.make_branch_and_tree('tree')
195
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
196
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
197
self.build_tree(['tree/' + p for p in paths])
198
tree.set_root_id('TREE_ROOT')
199
tree.add([p.rstrip('/') for p in paths], file_ids)
200
tree.commit('initial', rev_id='rev-1')
201
revision_id = 'rev-1'
202
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
203
t = self.get_transport('tree')
204
a_text = t.get_bytes('a')
205
a_sha = osutils.sha_string(a_text)
207
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
208
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
209
c_text = t.get_bytes('b/c')
210
c_sha = osutils.sha_string(c_text)
212
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
213
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
214
e_text = t.get_bytes('b/d/e')
215
e_sha = osutils.sha_string(e_text)
217
b_c_text = t.get_bytes('b-c')
218
b_c_sha = osutils.sha_string(b_c_text)
219
b_c_len = len(b_c_text)
220
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
221
f_text = t.get_bytes('f')
222
f_sha = osutils.sha_string(f_text)
224
null_stat = dirstate.DirState.NULLSTAT
226
'':(('', '', 'TREE_ROOT'), [
227
('d', '', 0, False, null_stat),
228
('d', '', 0, False, revision_id),
230
'a':(('', 'a', 'a-id'), [
231
('f', '', 0, False, null_stat),
232
('f', a_sha, a_len, False, revision_id),
234
'b':(('', 'b', 'b-id'), [
235
('d', '', 0, False, null_stat),
236
('d', '', 0, False, revision_id),
238
'b/c':(('b', 'c', 'c-id'), [
239
('f', '', 0, False, null_stat),
240
('f', c_sha, c_len, False, revision_id),
242
'b/d':(('b', 'd', 'd-id'), [
243
('d', '', 0, False, null_stat),
244
('d', '', 0, False, revision_id),
246
'b/d/e':(('b/d', 'e', 'e-id'), [
247
('f', '', 0, False, null_stat),
248
('f', e_sha, e_len, False, revision_id),
250
'b-c':(('', 'b-c', 'b-c-id'), [
251
('f', '', 0, False, null_stat),
252
('f', b_c_sha, b_c_len, False, revision_id),
254
'f':(('', 'f', 'f-id'), [
255
('f', '', 0, False, null_stat),
256
('f', f_sha, f_len, False, revision_id),
259
state = dirstate.DirState.from_tree(tree, 'dirstate')
264
# Use a different object, to make sure nothing is pre-cached in memory.
265
state = dirstate.DirState.on_file('dirstate')
267
self.addCleanup(state.unlock)
268
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
269
state._dirblock_state)
270
# This is code is only really tested if we actually have to make more
271
# than one read, so set the page size to something smaller.
272
# We want it to contain about 2.2 records, so that we have a couple
273
# records that we can read per attempt
274
state._bisect_page_size = 200
275
return tree, state, expected
277
def create_duplicated_dirstate(self):
278
"""Create a dirstate with a deleted and added entries.
280
This grabs a basic_dirstate, and then removes and re adds every entry
283
tree, state, expected = self.create_basic_dirstate()
284
# Now we will just remove and add every file so we get an extra entry
285
# per entry. Unversion in reverse order so we handle subdirs
286
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
287
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
288
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
290
# Update the expected dictionary.
291
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
292
orig = expected[path]
294
# This record was deleted in the current tree
295
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
297
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
298
# And didn't exist in the basis tree
299
expected[path2] = (new_key, [orig[1][0],
300
dirstate.DirState.NULL_PARENT_DETAILS])
302
# We will replace the 'dirstate' file underneath 'state', but that is
303
# okay as lock as we unlock 'state' first.
306
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
312
# But we need to leave state in a read-lock because we already have
313
# a cleanup scheduled
315
return tree, state, expected
317
def create_renamed_dirstate(self):
318
"""Create a dirstate with a few internal renames.
320
This takes the basic dirstate, and moves the paths around.
322
tree, state, expected = self.create_basic_dirstate()
324
tree.rename_one('a', 'b/g')
326
tree.rename_one('b/d', 'h')
328
old_a = expected['a']
329
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
330
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
331
('r', 'a', 0, False, '')])
332
old_d = expected['b/d']
333
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
334
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
335
('r', 'b/d', 0, False, '')])
337
old_e = expected['b/d/e']
338
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
340
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
341
('r', 'b/d/e', 0, False, '')])
345
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
352
return tree, state, expected
355
class TestTreeToDirState(TestCaseWithDirState):
357
def test_empty_to_dirstate(self):
358
"""We should be able to create a dirstate for an empty tree."""
359
# There are no files on disk and no parents
360
tree = self.make_branch_and_tree('tree')
361
expected_result = ([], [
362
(('', '', tree.path2id('')), # common details
363
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
365
state = dirstate.DirState.from_tree(tree, 'dirstate')
367
self.check_state_with_reopen(expected_result, state)
369
def test_1_parents_empty_to_dirstate(self):
370
# create a parent by doing a commit
371
tree = self.make_branch_and_tree('tree')
372
rev_id = tree.commit('first post').encode('utf8')
373
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
374
expected_result = ([rev_id], [
375
(('', '', tree.path2id('')), # common details
376
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
377
('d', '', 0, False, rev_id), # first parent details
379
state = dirstate.DirState.from_tree(tree, 'dirstate')
380
self.check_state_with_reopen(expected_result, state)
387
def test_2_parents_empty_to_dirstate(self):
388
# create a parent by doing a commit
389
tree = self.make_branch_and_tree('tree')
390
rev_id = tree.commit('first post')
391
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
392
rev_id2 = tree2.commit('second post', allow_pointless=True)
393
tree.merge_from_branch(tree2.branch)
394
expected_result = ([rev_id, rev_id2], [
395
(('', '', tree.path2id('')), # common details
396
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
397
('d', '', 0, False, rev_id), # first parent details
398
('d', '', 0, False, rev_id2), # second parent details
400
state = dirstate.DirState.from_tree(tree, 'dirstate')
401
self.check_state_with_reopen(expected_result, state)
408
def test_empty_unknowns_are_ignored_to_dirstate(self):
409
"""We should be able to create a dirstate for an empty tree."""
410
# There are no files on disk and no parents
411
tree = self.make_branch_and_tree('tree')
412
self.build_tree(['tree/unknown'])
413
expected_result = ([], [
414
(('', '', tree.path2id('')), # common details
415
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
417
state = dirstate.DirState.from_tree(tree, 'dirstate')
418
self.check_state_with_reopen(expected_result, state)
420
def get_tree_with_a_file(self):
421
tree = self.make_branch_and_tree('tree')
422
self.build_tree(['tree/a file'])
423
tree.add('a file', 'a file id')
426
def test_non_empty_no_parents_to_dirstate(self):
427
"""We should be able to create a dirstate for an empty tree."""
428
# There are files on disk and no parents
429
tree = self.get_tree_with_a_file()
430
expected_result = ([], [
431
(('', '', tree.path2id('')), # common details
432
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
434
(('', 'a file', 'a file id'), # common
435
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
438
state = dirstate.DirState.from_tree(tree, 'dirstate')
439
self.check_state_with_reopen(expected_result, state)
441
def test_1_parents_not_empty_to_dirstate(self):
442
# create a parent by doing a commit
443
tree = self.get_tree_with_a_file()
444
rev_id = tree.commit('first post').encode('utf8')
445
# change the current content to be different this will alter stat, sha
447
self.build_tree_contents([('tree/a file', 'new content\n')])
448
expected_result = ([rev_id], [
449
(('', '', tree.path2id('')), # common details
450
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
451
('d', '', 0, False, rev_id), # first parent details
453
(('', 'a file', 'a file id'), # common
454
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
455
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
456
rev_id), # first parent
459
state = dirstate.DirState.from_tree(tree, 'dirstate')
460
self.check_state_with_reopen(expected_result, state)
462
def test_2_parents_not_empty_to_dirstate(self):
463
# create a parent by doing a commit
464
tree = self.get_tree_with_a_file()
465
rev_id = tree.commit('first post').encode('utf8')
466
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
467
# change the current content to be different this will alter stat, sha
469
self.build_tree_contents([('tree2/a file', 'merge content\n')])
470
rev_id2 = tree2.commit('second post').encode('utf8')
471
tree.merge_from_branch(tree2.branch)
472
# change the current content to be different this will alter stat, sha
473
# and length again, giving us three distinct values:
474
self.build_tree_contents([('tree/a file', 'new content\n')])
475
expected_result = ([rev_id, rev_id2], [
476
(('', '', tree.path2id('')), # common details
477
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
478
('d', '', 0, False, rev_id), # first parent details
479
('d', '', 0, False, rev_id2), # second parent details
481
(('', 'a file', 'a file id'), # common
482
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
483
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
484
rev_id), # first parent
485
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
486
rev_id2), # second parent
489
state = dirstate.DirState.from_tree(tree, 'dirstate')
490
self.check_state_with_reopen(expected_result, state)
492
def test_colliding_fileids(self):
493
# test insertion of parents creating several entries at the same path.
494
# we used to have a bug where they could cause the dirstate to break
495
# its ordering invariants.
496
# create some trees to test from
499
tree = self.make_branch_and_tree('tree%d' % i)
500
self.build_tree(['tree%d/name' % i,])
501
tree.add(['name'], ['file-id%d' % i])
502
revision_id = 'revid-%d' % i
503
tree.commit('message', rev_id=revision_id)
504
parents.append((revision_id,
505
tree.branch.repository.revision_tree(revision_id)))
506
# now fold these trees into a dirstate
507
state = dirstate.DirState.initialize('dirstate')
509
state.set_parent_trees(parents, [])
515
class TestDirStateOnFile(TestCaseWithDirState):
517
def test_construct_with_path(self):
518
tree = self.make_branch_and_tree('tree')
519
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
520
# we want to be able to get the lines of the dirstate that we will
522
lines = state.get_lines()
524
self.build_tree_contents([('dirstate', ''.join(lines))])
526
# no parents, default tree content
527
expected_result = ([], [
528
(('', '', tree.path2id('')), # common details
529
# current tree details, but new from_tree skips statting, it
530
# uses set_state_from_inventory, and thus depends on the
532
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
535
state = dirstate.DirState.on_file('dirstate')
536
state.lock_write() # check_state_with_reopen will save() and unlock it
537
self.check_state_with_reopen(expected_result, state)
539
def test_can_save_clean_on_file(self):
540
tree = self.make_branch_and_tree('tree')
541
state = dirstate.DirState.from_tree(tree, 'dirstate')
543
# doing a save should work here as there have been no changes.
545
# TODO: stat it and check it hasn't changed; may require waiting
546
# for the state accuracy window.
550
def test_can_save_in_read_lock(self):
551
self.build_tree(['a-file'])
552
state = dirstate.DirState.initialize('dirstate')
554
# No stat and no sha1 sum.
555
state.add('a-file', 'a-file-id', 'file', None, '')
560
# Now open in readonly mode
561
state = dirstate.DirState.on_file('dirstate')
564
entry = state._get_entry(0, path_utf8='a-file')
565
# The current sha1 sum should be empty
566
self.assertEqual('', entry[1][0][1])
567
# We should have a real entry.
568
self.assertNotEqual((None, None), entry)
569
# Make sure everything is old enough
570
state._sha_cutoff_time()
571
state._cutoff_time += 10
572
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
573
# We should have gotten a real sha1
574
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
577
# The dirblock has been updated
578
self.assertEqual(sha1sum, entry[1][0][1])
579
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
580
state._dirblock_state)
583
# Now, since we are the only one holding a lock, we should be able
584
# to save and have it written to disk
589
# Re-open the file, and ensure that the state has been updated.
590
state = dirstate.DirState.on_file('dirstate')
593
entry = state._get_entry(0, path_utf8='a-file')
594
self.assertEqual(sha1sum, entry[1][0][1])
598
def test_save_fails_quietly_if_locked(self):
599
"""If dirstate is locked, save will fail without complaining."""
600
self.build_tree(['a-file'])
601
state = dirstate.DirState.initialize('dirstate')
603
# No stat and no sha1 sum.
604
state.add('a-file', 'a-file-id', 'file', None, '')
609
state = dirstate.DirState.on_file('dirstate')
612
entry = state._get_entry(0, path_utf8='a-file')
613
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
614
# We should have gotten a real sha1
615
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
617
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
618
state._dirblock_state)
620
# Now, before we try to save, grab another dirstate, and take out a
622
# TODO: jam 20070315 Ideally this would be locked by another
623
# process. To make sure the file is really OS locked.
624
state2 = dirstate.DirState.on_file('dirstate')
627
# This won't actually write anything, because it couldn't grab
628
# a write lock. But it shouldn't raise an error, either.
629
# TODO: jam 20070315 We should probably distinguish between
630
# being dirty because of 'update_entry'. And dirty
631
# because of real modification. So that save() *does*
632
# raise a real error if it fails when we have real
640
# The file on disk should not be modified.
641
state = dirstate.DirState.on_file('dirstate')
644
entry = state._get_entry(0, path_utf8='a-file')
645
self.assertEqual('', entry[1][0][1])
650
class TestDirStateInitialize(TestCaseWithDirState):
652
def test_initialize(self):
653
expected_result = ([], [
654
(('', '', 'TREE_ROOT'), # common details
655
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
658
state = dirstate.DirState.initialize('dirstate')
660
self.assertIsInstance(state, dirstate.DirState)
661
lines = state.get_lines()
664
# On win32 you can't read from a locked file, even within the same
665
# process. So we have to unlock and release before we check the file
667
self.assertFileEqual(''.join(lines), 'dirstate')
668
state.lock_read() # check_state_with_reopen will unlock
669
self.check_state_with_reopen(expected_result, state)
672
class TestDirStateManipulations(TestCaseWithDirState):
674
def test_set_state_from_inventory_no_content_no_parents(self):
675
# setting the current inventory is a slow but important api to support.
676
tree1 = self.make_branch_and_memory_tree('tree1')
680
revid1 = tree1.commit('foo').encode('utf8')
681
root_id = tree1.inventory.root.file_id
682
inv = tree1.inventory
685
expected_result = [], [
686
(('', '', root_id), [
687
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
688
state = dirstate.DirState.initialize('dirstate')
690
state.set_state_from_inventory(inv)
691
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
693
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
694
state._dirblock_state)
699
# This will unlock it
700
self.check_state_with_reopen(expected_result, state)
702
def test_set_state_from_inventory_preserves_hashcache(self):
703
# https://bugs.launchpad.net/bzr/+bug/146176
704
# set_state_from_inventory should preserve the stat and hash value for
705
# workingtree files that are not changed by the inventory.
707
tree = self.make_branch_and_tree('.')
708
# depends on the default format using dirstate...
711
# make a dirstate with some valid hashcache data
712
# file on disk, but that's not needed for this test
713
foo_contents = 'contents of foo'
714
self.build_tree_contents([('foo', foo_contents)])
715
tree.add('foo', 'foo-id')
717
foo_stat = os.stat('foo')
718
foo_packed = dirstate.pack_stat(foo_stat)
719
foo_sha = osutils.sha_string(foo_contents)
720
foo_size = len(foo_contents)
722
# should not be cached yet, because the file's too fresh
724
(('', 'foo', 'foo-id',),
725
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
726
tree._dirstate._get_entry(0, 'foo-id'))
727
# poke in some hashcache information - it wouldn't normally be
728
# stored because it's too fresh
729
tree._dirstate.update_minimal(
730
('', 'foo', 'foo-id'),
731
'f', False, foo_sha, foo_packed, foo_size, 'foo')
732
# now should be cached
734
(('', 'foo', 'foo-id',),
735
[('f', foo_sha, foo_size, False, foo_packed)]),
736
tree._dirstate._get_entry(0, 'foo-id'))
738
# extract the inventory, and add something to it
739
inv = tree._get_inventory()
740
# should see the file we poked in...
741
self.assertTrue(inv.has_id('foo-id'))
742
self.assertTrue(inv.has_filename('foo'))
743
inv.add_path('bar', 'file', 'bar-id')
744
tree._dirstate._validate()
745
# this used to cause it to lose its hashcache
746
tree._dirstate.set_state_from_inventory(inv)
747
tree._dirstate._validate()
753
# now check that the state still has the original hashcache value
754
state = tree._dirstate
756
foo_tuple = state._get_entry(0, path_utf8='foo')
758
(('', 'foo', 'foo-id',),
759
[('f', foo_sha, len(foo_contents), False,
760
dirstate.pack_stat(foo_stat))]),
766
def test_set_state_from_inventory_mixed_paths(self):
767
tree1 = self.make_branch_and_tree('tree1')
768
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
769
'tree1/a/b/foo', 'tree1/a-b/bar'])
772
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
773
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
774
tree1.commit('rev1', rev_id='rev1')
775
root_id = tree1.get_root_id()
776
inv = tree1.inventory
779
expected_result1 = [('', '', root_id, 'd'),
780
('', 'a', 'a-id', 'd'),
781
('', 'a-b', 'a-b-id', 'd'),
782
('a', 'b', 'b-id', 'd'),
783
('a/b', 'foo', 'foo-id', 'f'),
784
('a-b', 'bar', 'bar-id', 'f'),
786
expected_result2 = [('', '', root_id, 'd'),
787
('', 'a', 'a-id', 'd'),
788
('', 'a-b', 'a-b-id', 'd'),
789
('a-b', 'bar', 'bar-id', 'f'),
791
state = dirstate.DirState.initialize('dirstate')
793
state.set_state_from_inventory(inv)
795
for entry in state._iter_entries():
796
values.append(entry[0] + entry[1][0][:1])
797
self.assertEqual(expected_result1, values)
799
state.set_state_from_inventory(inv)
801
for entry in state._iter_entries():
802
values.append(entry[0] + entry[1][0][:1])
803
self.assertEqual(expected_result2, values)
807
def test_set_path_id_no_parents(self):
808
"""The id of a path can be changed trivally with no parents."""
809
state = dirstate.DirState.initialize('dirstate')
811
# check precondition to be sure the state does change appropriately.
813
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
814
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
815
list(state._iter_entries()))
816
state.set_path_id('', 'foobarbaz')
818
(('', '', 'foobarbaz'), [('d', '', 0, False,
819
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
820
self.assertEqual(expected_rows, list(state._iter_entries()))
821
# should work across save too
825
state = dirstate.DirState.on_file('dirstate')
829
self.assertEqual(expected_rows, list(state._iter_entries()))
833
def test_set_path_id_with_parents(self):
834
"""Set the root file id in a dirstate with parents"""
835
mt = self.make_branch_and_tree('mt')
836
# in case the default tree format uses a different root id
837
mt.set_root_id('TREE_ROOT')
838
mt.commit('foo', rev_id='parent-revid')
839
rt = mt.branch.repository.revision_tree('parent-revid')
840
state = dirstate.DirState.initialize('dirstate')
843
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
844
state.set_path_id('', 'foobarbaz')
846
# now see that it is what we expected
848
(('', '', 'TREE_ROOT'),
849
[('a', '', 0, False, ''),
850
('d', '', 0, False, 'parent-revid'),
852
(('', '', 'foobarbaz'),
853
[('d', '', 0, False, ''),
854
('a', '', 0, False, ''),
858
self.assertEqual(expected_rows, list(state._iter_entries()))
859
# should work across save too
863
# now flush & check we get the same
864
state = dirstate.DirState.on_file('dirstate')
868
self.assertEqual(expected_rows, list(state._iter_entries()))
871
# now change within an existing file-backed state
875
state.set_path_id('', 'tree-root-2')
881
def test_set_parent_trees_no_content(self):
882
# set_parent_trees is a slow but important api to support.
883
tree1 = self.make_branch_and_memory_tree('tree1')
887
revid1 = tree1.commit('foo')
890
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
891
tree2 = MemoryTree.create_on_branch(branch2)
894
revid2 = tree2.commit('foo')
895
root_id = tree2.inventory.root.file_id
898
state = dirstate.DirState.initialize('dirstate')
900
state.set_path_id('', root_id)
901
state.set_parent_trees(
902
((revid1, tree1.branch.repository.revision_tree(revid1)),
903
(revid2, tree2.branch.repository.revision_tree(revid2)),
904
('ghost-rev', None)),
906
# check we can reopen and use the dirstate after setting parent
913
state = dirstate.DirState.on_file('dirstate')
916
self.assertEqual([revid1, revid2, 'ghost-rev'],
917
state.get_parent_ids())
918
# iterating the entire state ensures that the state is parsable.
919
list(state._iter_entries())
920
# be sure that it sets not appends - change it
921
state.set_parent_trees(
922
((revid1, tree1.branch.repository.revision_tree(revid1)),
923
('ghost-rev', None)),
925
# and now put it back.
926
state.set_parent_trees(
927
((revid1, tree1.branch.repository.revision_tree(revid1)),
928
(revid2, tree2.branch.repository.revision_tree(revid2)),
929
('ghost-rev', tree2.branch.repository.revision_tree(None))),
931
self.assertEqual([revid1, revid2, 'ghost-rev'],
932
state.get_parent_ids())
933
# the ghost should be recorded as such by set_parent_trees.
934
self.assertEqual(['ghost-rev'], state.get_ghosts())
936
[(('', '', root_id), [
937
('d', '', 0, False, dirstate.DirState.NULLSTAT),
938
('d', '', 0, False, revid1),
939
('d', '', 0, False, revid2)
941
list(state._iter_entries()))
945
def test_set_parent_trees_file_missing_from_tree(self):
946
# Adding a parent tree may reference files not in the current state.
947
# they should get listed just once by id, even if they are in two
949
# set_parent_trees is a slow but important api to support.
950
tree1 = self.make_branch_and_memory_tree('tree1')
954
tree1.add(['a file'], ['file-id'], ['file'])
955
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
956
revid1 = tree1.commit('foo')
959
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
960
tree2 = MemoryTree.create_on_branch(branch2)
963
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
964
revid2 = tree2.commit('foo')
965
root_id = tree2.inventory.root.file_id
968
# check the layout in memory
969
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
970
(('', '', root_id), [
971
('d', '', 0, False, dirstate.DirState.NULLSTAT),
972
('d', '', 0, False, revid1.encode('utf8')),
973
('d', '', 0, False, revid2.encode('utf8'))
975
(('', 'a file', 'file-id'), [
976
('a', '', 0, False, ''),
977
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
978
revid1.encode('utf8')),
979
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
980
revid2.encode('utf8'))
983
state = dirstate.DirState.initialize('dirstate')
985
state.set_path_id('', root_id)
986
state.set_parent_trees(
987
((revid1, tree1.branch.repository.revision_tree(revid1)),
988
(revid2, tree2.branch.repository.revision_tree(revid2)),
994
# check_state_with_reopen will unlock
995
self.check_state_with_reopen(expected_result, state)
997
### add a path via _set_data - so we dont need delta work, just
998
# raw data in, and ensure that it comes out via get_lines happily.
1000
def test_add_path_to_root_no_parents_all_data(self):
1001
# The most trivial addition of a path is when there are no parents and
1002
# its in the root and all data about the file is supplied
1003
self.build_tree(['a file'])
1004
stat = os.lstat('a file')
1005
# the 1*20 is the sha1 pretend value.
1006
state = dirstate.DirState.initialize('dirstate')
1007
expected_entries = [
1008
(('', '', 'TREE_ROOT'), [
1009
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1011
(('', 'a file', 'a file id'), [
1012
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1016
state.add('a file', 'a file id', 'file', stat, '1'*20)
1017
# having added it, it should be in the output of iter_entries.
1018
self.assertEqual(expected_entries, list(state._iter_entries()))
1019
# saving and reloading should not affect this.
1023
state = dirstate.DirState.on_file('dirstate')
1026
self.assertEqual(expected_entries, list(state._iter_entries()))
1030
def test_add_path_to_unversioned_directory(self):
1031
"""Adding a path to an unversioned directory should error.
1033
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1034
once dirstate is stable and if it is merged with WorkingTree3, consider
1035
removing this copy of the test.
1037
self.build_tree(['unversioned/', 'unversioned/a file'])
1038
state = dirstate.DirState.initialize('dirstate')
1040
self.assertRaises(errors.NotVersionedError, state.add,
1041
'unversioned/a file', 'a file id', 'file', None, None)
1045
def test_add_directory_to_root_no_parents_all_data(self):
1046
# The most trivial addition of a dir is when there are no parents and
1047
# its in the root and all data about the file is supplied
1048
self.build_tree(['a dir/'])
1049
stat = os.lstat('a dir')
1050
expected_entries = [
1051
(('', '', 'TREE_ROOT'), [
1052
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1054
(('', 'a dir', 'a dir id'), [
1055
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1058
state = dirstate.DirState.initialize('dirstate')
1060
state.add('a dir', 'a dir id', 'directory', stat, None)
1061
# having added it, it should be in the output of iter_entries.
1062
self.assertEqual(expected_entries, list(state._iter_entries()))
1063
# saving and reloading should not affect this.
1067
state = dirstate.DirState.on_file('dirstate')
1071
self.assertEqual(expected_entries, list(state._iter_entries()))
1075
def test_add_symlink_to_root_no_parents_all_data(self):
1076
# The most trivial addition of a symlink when there are no parents and
1077
# its in the root and all data about the file is supplied
1078
# bzr doesn't support fake symlinks on windows, yet.
1079
self.requireFeature(SymlinkFeature)
1080
os.symlink('target', 'a link')
1081
stat = os.lstat('a link')
1082
expected_entries = [
1083
(('', '', 'TREE_ROOT'), [
1084
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1086
(('', 'a link', 'a link id'), [
1087
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1090
state = dirstate.DirState.initialize('dirstate')
1092
state.add('a link', 'a link id', 'symlink', stat, 'target')
1093
# having added it, it should be in the output of iter_entries.
1094
self.assertEqual(expected_entries, list(state._iter_entries()))
1095
# saving and reloading should not affect this.
1099
state = dirstate.DirState.on_file('dirstate')
1102
self.assertEqual(expected_entries, list(state._iter_entries()))
1106
def test_add_directory_and_child_no_parents_all_data(self):
1107
# after adding a directory, we should be able to add children to it.
1108
self.build_tree(['a dir/', 'a dir/a file'])
1109
dirstat = os.lstat('a dir')
1110
filestat = os.lstat('a dir/a file')
1111
expected_entries = [
1112
(('', '', 'TREE_ROOT'), [
1113
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1115
(('', 'a dir', 'a dir id'), [
1116
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1118
(('a dir', 'a file', 'a file id'), [
1119
('f', '1'*20, 25, False,
1120
dirstate.pack_stat(filestat)), # current tree details
1123
state = dirstate.DirState.initialize('dirstate')
1125
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1126
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
1127
# added it, it should be in the output of iter_entries.
1128
self.assertEqual(expected_entries, list(state._iter_entries()))
1129
# saving and reloading should not affect this.
1133
state = dirstate.DirState.on_file('dirstate')
1136
self.assertEqual(expected_entries, list(state._iter_entries()))
1140
def test_add_tree_reference(self):
1141
# make a dirstate and add a tree reference
1142
state = dirstate.DirState.initialize('dirstate')
1144
('', 'subdir', 'subdir-id'),
1145
[('t', 'subtree-123123', 0, False,
1146
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1149
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1150
entry = state._get_entry(0, 'subdir-id', 'subdir')
1151
self.assertEqual(entry, expected_entry)
1156
# now check we can read it back
1160
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1161
self.assertEqual(entry, entry2)
1162
self.assertEqual(entry, expected_entry)
1163
# and lookup by id should work too
1164
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1165
self.assertEqual(entry, expected_entry)
1169
def test_add_forbidden_names(self):
1170
state = dirstate.DirState.initialize('dirstate')
1171
self.addCleanup(state.unlock)
1172
self.assertRaises(errors.BzrError,
1173
state.add, '.', 'ass-id', 'directory', None, None)
1174
self.assertRaises(errors.BzrError,
1175
state.add, '..', 'ass-id', 'directory', None, None)
1178
class TestGetLines(TestCaseWithDirState):
1180
def test_get_line_with_2_rows(self):
1181
state = self.create_dirstate_with_root_and_subdir()
1183
self.assertEqual(['#bazaar dirstate flat format 3\n',
1184
'crc32: 41262208\n',
1188
'\x00\x00a-root-value\x00'
1189
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1190
'\x00subdir\x00subdir-id\x00'
1191
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1192
], state.get_lines())
1196
def test_entry_to_line(self):
1197
state = self.create_dirstate_with_root()
1200
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1201
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1202
state._entry_to_line(state._dirblocks[0][1][0]))
1206
def test_entry_to_line_with_parent(self):
1207
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1208
root_entry = ('', '', 'a-root-value'), [
1209
('d', '', 0, False, packed_stat), # current tree details
1210
# first: a pointer to the current location
1211
('a', 'dirname/basename', 0, False, ''),
1213
state = dirstate.DirState.initialize('dirstate')
1216
'\x00\x00a-root-value\x00'
1217
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1218
'a\x00dirname/basename\x000\x00n\x00',
1219
state._entry_to_line(root_entry))
1223
def test_entry_to_line_with_two_parents_at_different_paths(self):
1224
# / in the tree, at / in one parent and /dirname/basename in the other.
1225
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1226
root_entry = ('', '', 'a-root-value'), [
1227
('d', '', 0, False, packed_stat), # current tree details
1228
('d', '', 0, False, 'rev_id'), # first parent details
1229
# second: a pointer to the current location
1230
('a', 'dirname/basename', 0, False, ''),
1232
state = dirstate.DirState.initialize('dirstate')
1235
'\x00\x00a-root-value\x00'
1236
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1237
'd\x00\x000\x00n\x00rev_id\x00'
1238
'a\x00dirname/basename\x000\x00n\x00',
1239
state._entry_to_line(root_entry))
1243
def test_iter_entries(self):
1244
# we should be able to iterate the dirstate entries from end to end
1245
# this is for get_lines to be easy to read.
1246
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1248
root_entries = [(('', '', 'a-root-value'), [
1249
('d', '', 0, False, packed_stat), # current tree details
1251
dirblocks.append(('', root_entries))
1252
# add two files in the root
1253
subdir_entry = ('', 'subdir', 'subdir-id'), [
1254
('d', '', 0, False, packed_stat), # current tree details
1256
afile_entry = ('', 'afile', 'afile-id'), [
1257
('f', 'sha1value', 34, False, packed_stat), # current tree details
1259
dirblocks.append(('', [subdir_entry, afile_entry]))
1261
file_entry2 = ('subdir', '2file', '2file-id'), [
1262
('f', 'sha1value', 23, False, packed_stat), # current tree details
1264
dirblocks.append(('subdir', [file_entry2]))
1265
state = dirstate.DirState.initialize('dirstate')
1267
state._set_data([], dirblocks)
1268
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1270
self.assertEqual(expected_entries, list(state._iter_entries()))
1275
class TestGetBlockRowIndex(TestCaseWithDirState):
1277
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1278
file_present, state, dirname, basename, tree_index):
1279
self.assertEqual((block_index, row_index, dir_present, file_present),
1280
state._get_block_entry_index(dirname, basename, tree_index))
1282
block = state._dirblocks[block_index]
1283
self.assertEqual(dirname, block[0])
1284
if dir_present and file_present:
1285
row = state._dirblocks[block_index][1][row_index]
1286
self.assertEqual(dirname, row[0][0])
1287
self.assertEqual(basename, row[0][1])
1289
def test_simple_structure(self):
1290
state = self.create_dirstate_with_root_and_subdir()
1291
self.addCleanup(state.unlock)
1292
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1293
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1294
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1295
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1296
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1299
def test_complex_structure_exists(self):
1300
state = self.create_complex_dirstate()
1301
self.addCleanup(state.unlock)
1302
# Make sure we can find everything that exists
1303
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1304
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1305
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1306
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1307
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1308
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1309
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1310
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1311
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1312
'b', 'h\xc3\xa5', 0)
1314
def test_complex_structure_missing(self):
1315
state = self.create_complex_dirstate()
1316
self.addCleanup(state.unlock)
1317
# Make sure things would be inserted in the right locations
1318
# '_' comes before 'a'
1319
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1320
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1321
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1322
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1324
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1325
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1326
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1327
# This would be inserted between a/ and b/
1328
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1330
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1333
class TestGetEntry(TestCaseWithDirState):
1335
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1336
"""Check that the right entry is returned for a request to getEntry."""
1337
entry = state._get_entry(index, path_utf8=path)
1339
self.assertEqual((None, None), entry)
1342
self.assertEqual((dirname, basename, file_id), cur[:3])
1344
def test_simple_structure(self):
1345
state = self.create_dirstate_with_root_and_subdir()
1346
self.addCleanup(state.unlock)
1347
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1348
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1349
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1350
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1351
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1353
def test_complex_structure_exists(self):
1354
state = self.create_complex_dirstate()
1355
self.addCleanup(state.unlock)
1356
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1357
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1358
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1359
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1360
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1361
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1362
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1363
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1364
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1367
def test_complex_structure_missing(self):
1368
state = self.create_complex_dirstate()
1369
self.addCleanup(state.unlock)
1370
self.assertEntryEqual(None, None, None, state, '_', 0)
1371
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1372
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1373
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1375
def test_get_entry_uninitialized(self):
1376
"""Calling get_entry will load data if it needs to"""
1377
state = self.create_dirstate_with_root()
1383
state = dirstate.DirState.on_file('dirstate')
1386
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1387
state._header_state)
1388
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1389
state._dirblock_state)
1390
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1395
class TestDirstateSortOrder(TestCaseWithTransport):
1396
"""Test that DirState adds entries in the right order."""
1398
def test_add_sorting(self):
1399
"""Add entries in lexicographical order, we get path sorted order.
1401
This tests it to a depth of 4, to make sure we don't just get it right
1402
at a single depth. 'a/a' should come before 'a-a', even though it
1403
doesn't lexicographically.
1405
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1406
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1409
state = dirstate.DirState.initialize('dirstate')
1410
self.addCleanup(state.unlock)
1412
fake_stat = os.stat('dirstate')
1414
d_id = d.replace('/', '_')+'-id'
1415
file_path = d + '/f'
1416
file_id = file_path.replace('/', '_')+'-id'
1417
state.add(d, d_id, 'directory', fake_stat, null_sha)
1418
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1420
expected = ['', '', 'a',
1421
'a/a', 'a/a/a', 'a/a/a/a',
1422
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1424
split = lambda p:p.split('/')
1425
self.assertEqual(sorted(expected, key=split), expected)
1426
dirblock_names = [d[0] for d in state._dirblocks]
1427
self.assertEqual(expected, dirblock_names)
1429
def test_set_parent_trees_correct_order(self):
1430
"""After calling set_parent_trees() we should maintain the order."""
1431
dirs = ['a', 'a-a', 'a/a']
1433
state = dirstate.DirState.initialize('dirstate')
1434
self.addCleanup(state.unlock)
1436
fake_stat = os.stat('dirstate')
1438
d_id = d.replace('/', '_')+'-id'
1439
file_path = d + '/f'
1440
file_id = file_path.replace('/', '_')+'-id'
1441
state.add(d, d_id, 'directory', fake_stat, null_sha)
1442
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1444
expected = ['', '', 'a', 'a/a', 'a-a']
1445
dirblock_names = [d[0] for d in state._dirblocks]
1446
self.assertEqual(expected, dirblock_names)
1448
# *really* cheesy way to just get an empty tree
1449
repo = self.make_repository('repo')
1450
empty_tree = repo.revision_tree(None)
1451
state.set_parent_trees([('null:', empty_tree)], [])
1453
dirblock_names = [d[0] for d in state._dirblocks]
1454
self.assertEqual(expected, dirblock_names)
1457
class InstrumentedDirState(dirstate.DirState):
1458
"""An DirState with instrumented sha1 functionality."""
1460
def __init__(self, path):
1461
super(InstrumentedDirState, self).__init__(path)
1462
self._time_offset = 0
1464
# member is dynamically set in DirState.__init__ to turn on trace
1465
self._sha1_file = self._sha1_file_and_log
1467
def _sha_cutoff_time(self):
1468
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1469
self._cutoff_time = timestamp + self._time_offset
1471
def _sha1_file_and_log(self, abspath):
1472
self._log.append(('sha1', abspath))
1473
return osutils.sha_file_by_name(abspath)
1475
def _read_link(self, abspath, old_link):
1476
self._log.append(('read_link', abspath, old_link))
1477
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1479
def _lstat(self, abspath, entry):
1480
self._log.append(('lstat', abspath))
1481
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1483
def _is_executable(self, mode, old_executable):
1484
self._log.append(('is_exec', mode, old_executable))
1485
return super(InstrumentedDirState, self)._is_executable(mode,
1488
def adjust_time(self, secs):
1489
"""Move the clock forward or back.
1491
:param secs: The amount to adjust the clock by. Positive values make it
1492
seem as if we are in the future, negative values make it seem like we
1495
self._time_offset += secs
1496
self._cutoff_time = None
1499
class _FakeStat(object):
1500
"""A class with the same attributes as a real stat result."""
1502
def __init__(self, size, mtime, ctime, dev, ino, mode):
1504
self.st_mtime = mtime
1505
self.st_ctime = ctime
1511
class TestUpdateEntry(TestCaseWithDirState):
1512
"""Test the DirState.update_entry functions"""
1514
def get_state_with_a(self):
1515
"""Create a DirState tracking a single object named 'a'"""
1516
state = InstrumentedDirState.initialize('dirstate')
1517
self.addCleanup(state.unlock)
1518
state.add('a', 'a-id', 'file', None, '')
1519
entry = state._get_entry(0, path_utf8='a')
1522
def test_update_entry(self):
1523
state, entry = self.get_state_with_a()
1524
self.build_tree(['a'])
1525
# Add one where we don't provide the stat or sha already
1526
self.assertEqual(('', 'a', 'a-id'), entry[0])
1527
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1529
# Flush the buffers to disk
1531
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1532
state._dirblock_state)
1534
stat_value = os.lstat('a')
1535
packed_stat = dirstate.pack_stat(stat_value)
1536
link_or_sha1 = state.update_entry(entry, abspath='a',
1537
stat_value=stat_value)
1538
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1541
# The dirblock entry should not cache the file's sha1
1542
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1544
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1545
state._dirblock_state)
1546
mode = stat_value.st_mode
1547
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1550
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1551
state._dirblock_state)
1553
# If we do it again right away, we don't know if the file has changed
1554
# so we will re-read the file. Roll the clock back so the file is
1555
# guaranteed to look too new.
1556
state.adjust_time(-10)
1558
link_or_sha1 = state.update_entry(entry, abspath='a',
1559
stat_value=stat_value)
1560
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1561
('sha1', 'a'), ('is_exec', mode, False),
1563
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1565
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1566
state._dirblock_state)
1567
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1571
# However, if we move the clock forward so the file is considered
1572
# "stable", it should just cache the value.
1573
state.adjust_time(+20)
1574
link_or_sha1 = state.update_entry(entry, abspath='a',
1575
stat_value=stat_value)
1576
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1578
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1579
('sha1', 'a'), ('is_exec', mode, False),
1580
('sha1', 'a'), ('is_exec', mode, False),
1582
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1585
# Subsequent calls will just return the cached value
1586
link_or_sha1 = state.update_entry(entry, abspath='a',
1587
stat_value=stat_value)
1588
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1590
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1591
('sha1', 'a'), ('is_exec', mode, False),
1592
('sha1', 'a'), ('is_exec', mode, False),
1594
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1597
def test_update_entry_symlink(self):
1598
"""Update entry should read symlinks."""
1599
self.requireFeature(SymlinkFeature)
1600
state, entry = self.get_state_with_a()
1602
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1603
state._dirblock_state)
1604
os.symlink('target', 'a')
1606
state.adjust_time(-10) # Make the symlink look new
1607
stat_value = os.lstat('a')
1608
packed_stat = dirstate.pack_stat(stat_value)
1609
link_or_sha1 = state.update_entry(entry, abspath='a',
1610
stat_value=stat_value)
1611
self.assertEqual('target', link_or_sha1)
1612
self.assertEqual([('read_link', 'a', '')], state._log)
1613
# Dirblock is not updated (the link is too new)
1614
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1616
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1617
state._dirblock_state)
1619
# Because the stat_value looks new, we should re-read the target
1620
link_or_sha1 = state.update_entry(entry, abspath='a',
1621
stat_value=stat_value)
1622
self.assertEqual('target', link_or_sha1)
1623
self.assertEqual([('read_link', 'a', ''),
1624
('read_link', 'a', ''),
1626
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1628
state.adjust_time(+20) # Skip into the future, all files look old
1629
link_or_sha1 = state.update_entry(entry, abspath='a',
1630
stat_value=stat_value)
1631
self.assertEqual('target', link_or_sha1)
1632
# We need to re-read the link because only now can we cache it
1633
self.assertEqual([('read_link', 'a', ''),
1634
('read_link', 'a', ''),
1635
('read_link', 'a', ''),
1637
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1640
# Another call won't re-read the link
1641
self.assertEqual([('read_link', 'a', ''),
1642
('read_link', 'a', ''),
1643
('read_link', 'a', ''),
1645
link_or_sha1 = state.update_entry(entry, abspath='a',
1646
stat_value=stat_value)
1647
self.assertEqual('target', link_or_sha1)
1648
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1651
def do_update_entry(self, state, entry, abspath):
1652
stat_value = os.lstat(abspath)
1653
return state.update_entry(entry, abspath, stat_value)
1655
def test_update_entry_dir(self):
1656
state, entry = self.get_state_with_a()
1657
self.build_tree(['a/'])
1658
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1660
def test_update_entry_dir_unchanged(self):
1661
state, entry = self.get_state_with_a()
1662
self.build_tree(['a/'])
1663
state.adjust_time(+20)
1664
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1665
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1666
state._dirblock_state)
1668
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1669
state._dirblock_state)
1670
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1671
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1672
state._dirblock_state)
1674
def test_update_entry_file_unchanged(self):
1675
state, entry = self.get_state_with_a()
1676
self.build_tree(['a'])
1677
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1678
state.adjust_time(+20)
1679
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1680
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1681
state._dirblock_state)
1683
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1684
state._dirblock_state)
1685
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1686
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1687
state._dirblock_state)
1689
def create_and_test_file(self, state, entry):
1690
"""Create a file at 'a' and verify the state finds it.
1692
The state should already be versioning *something* at 'a'. This makes
1693
sure that state.update_entry recognizes it as a file.
1695
self.build_tree(['a'])
1696
stat_value = os.lstat('a')
1697
packed_stat = dirstate.pack_stat(stat_value)
1699
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1700
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1702
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1706
def create_and_test_dir(self, state, entry):
1707
"""Create a directory at 'a' and verify the state finds it.
1709
The state should already be versioning *something* at 'a'. This makes
1710
sure that state.update_entry recognizes it as a directory.
1712
self.build_tree(['a/'])
1713
stat_value = os.lstat('a')
1714
packed_stat = dirstate.pack_stat(stat_value)
1716
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1717
self.assertIs(None, link_or_sha1)
1718
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1722
def create_and_test_symlink(self, state, entry):
1723
"""Create a symlink at 'a' and verify the state finds it.
1725
The state should already be versioning *something* at 'a'. This makes
1726
sure that state.update_entry recognizes it as a symlink.
1728
This should not be called if this platform does not have symlink
1731
# caller should care about skipping test on platforms without symlinks
1732
os.symlink('path/to/foo', 'a')
1734
stat_value = os.lstat('a')
1735
packed_stat = dirstate.pack_stat(stat_value)
1737
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1738
self.assertEqual('path/to/foo', link_or_sha1)
1739
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1743
def test_update_file_to_dir(self):
1744
"""If a file changes to a directory we return None for the sha.
1745
We also update the inventory record.
1747
state, entry = self.get_state_with_a()
1748
# The file sha1 won't be cached unless the file is old
1749
state.adjust_time(+10)
1750
self.create_and_test_file(state, entry)
1752
self.create_and_test_dir(state, entry)
1754
def test_update_file_to_symlink(self):
1755
"""File becomes a symlink"""
1756
self.requireFeature(SymlinkFeature)
1757
state, entry = self.get_state_with_a()
1758
# The file sha1 won't be cached unless the file is old
1759
state.adjust_time(+10)
1760
self.create_and_test_file(state, entry)
1762
self.create_and_test_symlink(state, entry)
1764
def test_update_dir_to_file(self):
1765
"""Directory becoming a file updates the entry."""
1766
state, entry = self.get_state_with_a()
1767
# The file sha1 won't be cached unless the file is old
1768
state.adjust_time(+10)
1769
self.create_and_test_dir(state, entry)
1771
self.create_and_test_file(state, entry)
1773
def test_update_dir_to_symlink(self):
1774
"""Directory becomes a symlink"""
1775
self.requireFeature(SymlinkFeature)
1776
state, entry = self.get_state_with_a()
1777
# The symlink target won't be cached if it isn't old
1778
state.adjust_time(+10)
1779
self.create_and_test_dir(state, entry)
1781
self.create_and_test_symlink(state, entry)
1783
def test_update_symlink_to_file(self):
1784
"""Symlink becomes a file"""
1785
self.requireFeature(SymlinkFeature)
1786
state, entry = self.get_state_with_a()
1787
# The symlink and file info won't be cached unless old
1788
state.adjust_time(+10)
1789
self.create_and_test_symlink(state, entry)
1791
self.create_and_test_file(state, entry)
1793
def test_update_symlink_to_dir(self):
1794
"""Symlink becomes a directory"""
1795
self.requireFeature(SymlinkFeature)
1796
state, entry = self.get_state_with_a()
1797
# The symlink target won't be cached if it isn't old
1798
state.adjust_time(+10)
1799
self.create_and_test_symlink(state, entry)
1801
self.create_and_test_dir(state, entry)
1803
def test__is_executable_win32(self):
1804
state, entry = self.get_state_with_a()
1805
self.build_tree(['a'])
1807
# Make sure we are using the win32 implementation of _is_executable
1808
state._is_executable = state._is_executable_win32
1810
# The file on disk is not executable, but we are marking it as though
1811
# it is. With _is_executable_win32 we ignore what is on disk.
1812
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1814
stat_value = os.lstat('a')
1815
packed_stat = dirstate.pack_stat(stat_value)
1817
state.adjust_time(-10) # Make sure everything is new
1818
state.update_entry(entry, abspath='a', stat_value=stat_value)
1820
# The row is updated, but the executable bit stays set.
1821
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1824
# Make the disk object look old enough to cache
1825
state.adjust_time(+20)
1826
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1827
state.update_entry(entry, abspath='a', stat_value=stat_value)
1828
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1831
class TestPackStat(TestCaseWithTransport):
1833
def assertPackStat(self, expected, stat_value):
1834
"""Check the packed and serialized form of a stat value."""
1835
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1837
def test_pack_stat_int(self):
1838
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1839
# Make sure that all parameters have an impact on the packed stat.
1840
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1843
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1844
st.st_mtime = 1172758620
1846
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1847
st.st_ctime = 1172758630
1849
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1852
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1853
st.st_ino = 6499540L
1855
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1856
st.st_mode = 0100744
1858
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1860
def test_pack_stat_float(self):
1861
"""On some platforms mtime and ctime are floats.
1863
Make sure we don't get warnings or errors, and that we ignore changes <
1866
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1867
777L, 6499538L, 0100644)
1868
# These should all be the same as the integer counterparts
1869
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1870
st.st_mtime = 1172758620.0
1872
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1873
st.st_ctime = 1172758630.0
1875
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1876
# fractional seconds are discarded, so no change from above
1877
st.st_mtime = 1172758620.453
1878
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1879
st.st_ctime = 1172758630.228
1880
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1883
class TestBisect(TestCaseWithDirState):
1884
"""Test the ability to bisect into the disk format."""
1886
def assertBisect(self, expected_map, map_keys, state, paths):
1887
"""Assert that bisecting for paths returns the right result.
1889
:param expected_map: A map from key => entry value
1890
:param map_keys: The keys to expect for each path
1891
:param state: The DirState object.
1892
:param paths: A list of paths, these will automatically be split into
1893
(dir, name) tuples, and sorted according to how _bisect
1896
result = state._bisect(paths)
1897
# For now, results are just returned in whatever order we read them.
1898
# We could sort by (dir, name, file_id) or something like that, but in
1899
# the end it would still be fairly arbitrary, and we don't want the
1900
# extra overhead if we can avoid it. So sort everything to make sure
1902
assert len(map_keys) == len(paths)
1904
for path, keys in zip(paths, map_keys):
1906
# This should not be present in the output
1908
expected[path] = sorted(expected_map[k] for k in keys)
1910
# The returned values are just arranged randomly based on when they
1911
# were read, for testing, make sure it is properly sorted.
1915
self.assertEqual(expected, result)
1917
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1918
"""Assert that bisecting for dirbblocks returns the right result.
1920
:param expected_map: A map from key => expected values
1921
:param map_keys: A nested list of paths we expect to be returned.
1922
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1923
:param state: The DirState object.
1924
:param paths: A list of directories
1926
result = state._bisect_dirblocks(paths)
1927
assert len(map_keys) == len(paths)
1930
for path, keys in zip(paths, map_keys):
1932
# This should not be present in the output
1934
expected[path] = sorted(expected_map[k] for k in keys)
1938
self.assertEqual(expected, result)
1940
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1941
"""Assert the return value of a recursive bisection.
1943
:param expected_map: A map from key => entry value
1944
:param map_keys: A list of paths we expect to be returned.
1945
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1946
:param state: The DirState object.
1947
:param paths: A list of files and directories. It will be broken up
1948
into (dir, name) pairs and sorted before calling _bisect_recursive.
1951
for key in map_keys:
1952
entry = expected_map[key]
1953
dir_name_id, trees_info = entry
1954
expected[dir_name_id] = trees_info
1956
result = state._bisect_recursive(paths)
1958
self.assertEqual(expected, result)
1960
def test_bisect_each(self):
1961
"""Find a single record using bisect."""
1962
tree, state, expected = self.create_basic_dirstate()
1964
# Bisect should return the rows for the specified files.
1965
self.assertBisect(expected, [['']], state, [''])
1966
self.assertBisect(expected, [['a']], state, ['a'])
1967
self.assertBisect(expected, [['b']], state, ['b'])
1968
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1969
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1970
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1971
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1972
self.assertBisect(expected, [['f']], state, ['f'])
1974
def test_bisect_multi(self):
1975
"""Bisect can be used to find multiple records at the same time."""
1976
tree, state, expected = self.create_basic_dirstate()
1977
# Bisect should be capable of finding multiple entries at the same time
1978
self.assertBisect(expected, [['a'], ['b'], ['f']],
1979
state, ['a', 'b', 'f'])
1980
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1981
state, ['f', 'b/d', 'b/d/e'])
1982
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1983
state, ['b', 'b-c', 'b/c'])
1985
def test_bisect_one_page(self):
1986
"""Test bisect when there is only 1 page to read"""
1987
tree, state, expected = self.create_basic_dirstate()
1988
state._bisect_page_size = 5000
1989
self.assertBisect(expected,[['']], state, [''])
1990
self.assertBisect(expected,[['a']], state, ['a'])
1991
self.assertBisect(expected,[['b']], state, ['b'])
1992
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1993
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1994
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1995
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1996
self.assertBisect(expected,[['f']], state, ['f'])
1997
self.assertBisect(expected,[['a'], ['b'], ['f']],
1998
state, ['a', 'b', 'f'])
1999
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
2000
state, ['b/d', 'b/d/e', 'f'])
2001
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
2002
state, ['b', 'b/c', 'b-c'])
2004
def test_bisect_duplicate_paths(self):
2005
"""When bisecting for a path, handle multiple entries."""
2006
tree, state, expected = self.create_duplicated_dirstate()
2008
# Now make sure that both records are properly returned.
2009
self.assertBisect(expected, [['']], state, [''])
2010
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
2011
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
2012
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
2013
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
2014
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
2016
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
2017
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
2019
def test_bisect_page_size_too_small(self):
2020
"""If the page size is too small, we will auto increase it."""
2021
tree, state, expected = self.create_basic_dirstate()
2022
state._bisect_page_size = 50
2023
self.assertBisect(expected, [None], state, ['b/e'])
2024
self.assertBisect(expected, [['a']], state, ['a'])
2025
self.assertBisect(expected, [['b']], state, ['b'])
2026
self.assertBisect(expected, [['b/c']], state, ['b/c'])
2027
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2028
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2029
self.assertBisect(expected, [['b-c']], state, ['b-c'])
2030
self.assertBisect(expected, [['f']], state, ['f'])
2032
def test_bisect_missing(self):
2033
"""Test that bisect return None if it cannot find a path."""
2034
tree, state, expected = self.create_basic_dirstate()
2035
self.assertBisect(expected, [None], state, ['foo'])
2036
self.assertBisect(expected, [None], state, ['b/foo'])
2037
self.assertBisect(expected, [None], state, ['bar/foo'])
2038
self.assertBisect(expected, [None], state, ['b-c/foo'])
2040
self.assertBisect(expected, [['a'], None, ['b/d']],
2041
state, ['a', 'foo', 'b/d'])
2043
def test_bisect_rename(self):
2044
"""Check that we find a renamed row."""
2045
tree, state, expected = self.create_renamed_dirstate()
2047
# Search for the pre and post renamed entries
2048
self.assertBisect(expected, [['a']], state, ['a'])
2049
self.assertBisect(expected, [['b/g']], state, ['b/g'])
2050
self.assertBisect(expected, [['b/d']], state, ['b/d'])
2051
self.assertBisect(expected, [['h']], state, ['h'])
2053
# What about b/d/e? shouldn't that also get 2 directory entries?
2054
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
2055
self.assertBisect(expected, [['h/e']], state, ['h/e'])
2057
def test_bisect_dirblocks(self):
2058
tree, state, expected = self.create_duplicated_dirstate()
2059
self.assertBisectDirBlocks(expected,
2060
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2062
self.assertBisectDirBlocks(expected,
2063
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2064
self.assertBisectDirBlocks(expected,
2065
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2066
self.assertBisectDirBlocks(expected,
2067
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
2068
['b/c', 'b/c2', 'b/d', 'b/d2'],
2069
['b/d/e', 'b/d/e2'],
2070
], state, ['', 'b', 'b/d'])
2072
def test_bisect_dirblocks_missing(self):
2073
tree, state, expected = self.create_basic_dirstate()
2074
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
2075
state, ['b/d', 'b/e'])
2076
# Files don't show up in this search
2077
self.assertBisectDirBlocks(expected, [None], state, ['a'])
2078
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
2079
self.assertBisectDirBlocks(expected, [None], state, ['c'])
2080
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
2081
self.assertBisectDirBlocks(expected, [None], state, ['f'])
2083
def test_bisect_recursive_each(self):
2084
tree, state, expected = self.create_basic_dirstate()
2085
self.assertBisectRecursive(expected, ['a'], state, ['a'])
2086
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2087
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2088
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2089
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2091
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2093
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2097
def test_bisect_recursive_multiple(self):
2098
tree, state, expected = self.create_basic_dirstate()
2099
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2100
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2101
state, ['b/d', 'b/d/e'])
2103
def test_bisect_recursive_missing(self):
2104
tree, state, expected = self.create_basic_dirstate()
2105
self.assertBisectRecursive(expected, [], state, ['d'])
2106
self.assertBisectRecursive(expected, [], state, ['b/e'])
2107
self.assertBisectRecursive(expected, [], state, ['g'])
2108
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2110
def test_bisect_recursive_renamed(self):
2111
tree, state, expected = self.create_renamed_dirstate()
2113
# Looking for either renamed item should find the other
2114
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2115
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2116
# Looking in the containing directory should find the rename target,
2117
# and anything in a subdir of the renamed target.
2118
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2119
'b/d/e', 'b/g', 'h', 'h/e'],
2123
class TestDirstateValidation(TestCaseWithDirState):
2125
def test_validate_correct_dirstate(self):
2126
state = self.create_complex_dirstate()
2129
# and make sure we can also validate with a read lock
2136
def test_dirblock_not_sorted(self):
2137
tree, state, expected = self.create_renamed_dirstate()
2138
state._read_dirblocks_if_needed()
2139
last_dirblock = state._dirblocks[-1]
2140
# we're appending to the dirblock, but this name comes before some of
2141
# the existing names; that's wrong
2142
last_dirblock[1].append(
2143
(('h', 'aaaa', 'a-id'),
2144
[('a', '', 0, False, ''),
2145
('a', '', 0, False, '')]))
2146
e = self.assertRaises(AssertionError,
2148
self.assertContainsRe(str(e), 'not sorted')
2150
def test_dirblock_name_mismatch(self):
2151
tree, state, expected = self.create_renamed_dirstate()
2152
state._read_dirblocks_if_needed()
2153
last_dirblock = state._dirblocks[-1]
2154
# add an entry with the wrong directory name
2155
last_dirblock[1].append(
2157
[('a', '', 0, False, ''),
2158
('a', '', 0, False, '')]))
2159
e = self.assertRaises(AssertionError,
2161
self.assertContainsRe(str(e),
2162
"doesn't match directory name")
2164
def test_dirblock_missing_rename(self):
2165
tree, state, expected = self.create_renamed_dirstate()
2166
state._read_dirblocks_if_needed()
2167
last_dirblock = state._dirblocks[-1]
2168
# make another entry for a-id, without a correct 'r' pointer to
2169
# the real occurrence in the working tree
2170
last_dirblock[1].append(
2171
(('h', 'z', 'a-id'),
2172
[('a', '', 0, False, ''),
2173
('a', '', 0, False, '')]))
2174
e = self.assertRaises(AssertionError,
2176
self.assertContainsRe(str(e),
2177
'file a-id is absent in row')
2180
class TestDirstateTreeReference(TestCaseWithDirState):
2182
def test_reference_revision_is_none(self):
2183
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2184
subtree = self.make_branch_and_tree('tree/subtree',
2185
format='dirstate-with-subtree')
2186
subtree.set_root_id('subtree')
2187
tree.add_reference(subtree)
2189
state = dirstate.DirState.from_tree(tree, 'dirstate')
2190
key = ('', 'subtree', 'subtree')
2191
expected = ('', [(key,
2192
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2195
self.assertEqual(expected, state._find_block(key))