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()))
176
del state # Callers should unlock
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.
194
tree = self.make_branch_and_tree('tree')
195
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
196
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-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().clone('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
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
218
f_text = t.get_bytes('f')
219
f_sha = osutils.sha_string(f_text)
221
null_stat = dirstate.DirState.NULLSTAT
223
'':(('', '', 'TREE_ROOT'), [
224
('d', '', 0, False, null_stat),
225
('d', '', 0, False, revision_id),
227
'a':(('', 'a', 'a-id'), [
228
('f', '', 0, False, null_stat),
229
('f', a_sha, a_len, False, revision_id),
231
'b':(('', 'b', 'b-id'), [
232
('d', '', 0, False, null_stat),
233
('d', '', 0, False, revision_id),
235
'b/c':(('b', 'c', 'c-id'), [
236
('f', '', 0, False, null_stat),
237
('f', c_sha, c_len, False, revision_id),
239
'b/d':(('b', 'd', 'd-id'), [
240
('d', '', 0, False, null_stat),
241
('d', '', 0, False, revision_id),
243
'b/d/e':(('b/d', 'e', 'e-id'), [
244
('f', '', 0, False, null_stat),
245
('f', e_sha, e_len, False, revision_id),
247
'f':(('', 'f', 'f-id'), [
248
('f', '', 0, False, null_stat),
249
('f', f_sha, f_len, False, revision_id),
252
state = dirstate.DirState.from_tree(tree, 'dirstate')
257
# Use a different object, to make sure nothing is pre-cached in memory.
258
state = dirstate.DirState.on_file('dirstate')
260
self.addCleanup(state.unlock)
261
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
262
state._dirblock_state)
263
# This is code is only really tested if we actually have to make more
264
# than one read, so set the page size to something smaller.
265
# We want it to contain about 2.2 records, so that we have a couple
266
# records that we can read per attempt
267
state._bisect_page_size = 200
268
return tree, state, expected
270
def create_duplicated_dirstate(self):
271
"""Create a dirstate with a deleted and added entries.
273
This grabs a basic_dirstate, and then removes and re adds every entry
276
tree, state, expected = self.create_basic_dirstate()
277
# Now we will just remove and add every file so we get an extra entry
278
# per entry. Unversion in reverse order so we handle subdirs
279
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
280
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
281
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
283
# Update the expected dictionary.
284
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
285
orig = expected[path]
287
# This record was deleted in the current tree
288
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
290
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
291
# And didn't exist in the basis tree
292
expected[path2] = (new_key, [orig[1][0],
293
dirstate.DirState.NULL_PARENT_DETAILS])
295
# We will replace the 'dirstate' file underneath 'state', but that is
296
# okay as lock as we unlock 'state' first.
299
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
305
# But we need to leave state in a read-lock because we already have
306
# a cleanup scheduled
308
return tree, state, expected
310
def create_renamed_dirstate(self):
311
"""Create a dirstate with a few internal renames.
313
This takes the basic dirstate, and moves the paths around.
315
tree, state, expected = self.create_basic_dirstate()
317
tree.rename_one('a', 'b/g')
319
tree.rename_one('b/d', 'h')
321
old_a = expected['a']
322
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
323
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
324
('r', 'a', 0, False, '')])
325
old_d = expected['b/d']
326
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
327
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
328
('r', 'b/d', 0, False, '')])
330
old_e = expected['b/d/e']
331
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
333
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
334
('r', 'b/d/e', 0, False, '')])
338
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
345
return tree, state, expected
347
class TestTreeToDirState(TestCaseWithDirState):
349
def test_empty_to_dirstate(self):
350
"""We should be able to create a dirstate for an empty tree."""
351
# There are no files on disk and no parents
352
tree = self.make_branch_and_tree('tree')
353
expected_result = ([], [
354
(('', '', tree.path2id('')), # common details
355
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
357
state = dirstate.DirState.from_tree(tree, 'dirstate')
359
self.check_state_with_reopen(expected_result, state)
361
def test_1_parents_empty_to_dirstate(self):
362
# create a parent by doing a commit
363
tree = self.make_branch_and_tree('tree')
364
rev_id = tree.commit('first post').encode('utf8')
365
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
366
expected_result = ([rev_id], [
367
(('', '', tree.path2id('')), # common details
368
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
369
('d', '', 0, False, rev_id), # first parent details
371
state = dirstate.DirState.from_tree(tree, 'dirstate')
372
self.check_state_with_reopen(expected_result, state)
379
def test_2_parents_empty_to_dirstate(self):
380
# create a parent by doing a commit
381
tree = self.make_branch_and_tree('tree')
382
rev_id = tree.commit('first post')
383
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
384
rev_id2 = tree2.commit('second post', allow_pointless=True)
385
tree.merge_from_branch(tree2.branch)
386
expected_result = ([rev_id, rev_id2], [
387
(('', '', tree.path2id('')), # common details
388
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
389
('d', '', 0, False, rev_id), # first parent details
390
('d', '', 0, False, rev_id2), # second parent details
392
state = dirstate.DirState.from_tree(tree, 'dirstate')
393
self.check_state_with_reopen(expected_result, state)
400
def test_empty_unknowns_are_ignored_to_dirstate(self):
401
"""We should be able to create a dirstate for an empty tree."""
402
# There are no files on disk and no parents
403
tree = self.make_branch_and_tree('tree')
404
self.build_tree(['tree/unknown'])
405
expected_result = ([], [
406
(('', '', tree.path2id('')), # common details
407
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
409
state = dirstate.DirState.from_tree(tree, 'dirstate')
410
self.check_state_with_reopen(expected_result, state)
412
def get_tree_with_a_file(self):
413
tree = self.make_branch_and_tree('tree')
414
self.build_tree(['tree/a file'])
415
tree.add('a file', 'a file id')
418
def test_non_empty_no_parents_to_dirstate(self):
419
"""We should be able to create a dirstate for an empty tree."""
420
# There are files on disk and no parents
421
tree = self.get_tree_with_a_file()
422
expected_result = ([], [
423
(('', '', tree.path2id('')), # common details
424
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
426
(('', 'a file', 'a file id'), # common
427
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
430
state = dirstate.DirState.from_tree(tree, 'dirstate')
431
self.check_state_with_reopen(expected_result, state)
433
def test_1_parents_not_empty_to_dirstate(self):
434
# create a parent by doing a commit
435
tree = self.get_tree_with_a_file()
436
rev_id = tree.commit('first post').encode('utf8')
437
# change the current content to be different this will alter stat, sha
439
self.build_tree_contents([('tree/a file', 'new content\n')])
440
expected_result = ([rev_id], [
441
(('', '', tree.path2id('')), # common details
442
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
443
('d', '', 0, False, rev_id), # first parent details
445
(('', 'a file', 'a file id'), # common
446
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
447
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
448
rev_id), # first parent
451
state = dirstate.DirState.from_tree(tree, 'dirstate')
452
self.check_state_with_reopen(expected_result, state)
454
def test_2_parents_not_empty_to_dirstate(self):
455
# create a parent by doing a commit
456
tree = self.get_tree_with_a_file()
457
rev_id = tree.commit('first post').encode('utf8')
458
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
459
# change the current content to be different this will alter stat, sha
461
self.build_tree_contents([('tree2/a file', 'merge content\n')])
462
rev_id2 = tree2.commit('second post').encode('utf8')
463
tree.merge_from_branch(tree2.branch)
464
# change the current content to be different this will alter stat, sha
465
# and length again, giving us three distinct values:
466
self.build_tree_contents([('tree/a file', 'new content\n')])
467
expected_result = ([rev_id, rev_id2], [
468
(('', '', tree.path2id('')), # common details
469
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
470
('d', '', 0, False, rev_id), # first parent details
471
('d', '', 0, False, rev_id2), # second parent details
473
(('', 'a file', 'a file id'), # common
474
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
475
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
476
rev_id), # first parent
477
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
478
rev_id2), # second parent
481
state = dirstate.DirState.from_tree(tree, 'dirstate')
482
self.check_state_with_reopen(expected_result, state)
484
def test_colliding_fileids(self):
485
# test insertion of parents creating several entries at the same path.
486
# we used to have a bug where they could cause the dirstate to break
487
# its ordering invariants.
488
# create some trees to test from
491
tree = self.make_branch_and_tree('tree%d' % i)
492
self.build_tree(['tree%d/name' % i,])
493
tree.add(['name'], ['file-id%d' % i])
494
revision_id = 'revid-%d' % i
495
tree.commit('message', rev_id=revision_id)
496
parents.append((revision_id,
497
tree.branch.repository.revision_tree(revision_id)))
498
# now fold these trees into a dirstate
499
state = dirstate.DirState.initialize('dirstate')
501
state.set_parent_trees(parents, [])
507
class TestDirStateOnFile(TestCaseWithDirState):
509
def test_construct_with_path(self):
510
tree = self.make_branch_and_tree('tree')
511
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
512
# we want to be able to get the lines of the dirstate that we will
514
lines = state.get_lines()
516
self.build_tree_contents([('dirstate', ''.join(lines))])
518
# no parents, default tree content
519
expected_result = ([], [
520
(('', '', tree.path2id('')), # common details
521
# current tree details, but new from_tree skips statting, it
522
# uses set_state_from_inventory, and thus depends on the
524
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
527
state = dirstate.DirState.on_file('dirstate')
528
state.lock_write() # check_state_with_reopen will save() and unlock it
529
self.check_state_with_reopen(expected_result, state)
531
def test_can_save_clean_on_file(self):
532
tree = self.make_branch_and_tree('tree')
533
state = dirstate.DirState.from_tree(tree, 'dirstate')
535
# doing a save should work here as there have been no changes.
537
# TODO: stat it and check it hasn't changed; may require waiting
538
# for the state accuracy window.
542
def test_can_save_in_read_lock(self):
543
self.build_tree(['a-file'])
544
state = dirstate.DirState.initialize('dirstate')
546
# No stat and no sha1 sum.
547
state.add('a-file', 'a-file-id', 'file', None, '')
552
# Now open in readonly mode
553
state = dirstate.DirState.on_file('dirstate')
556
entry = state._get_entry(0, path_utf8='a-file')
557
# The current sha1 sum should be empty
558
self.assertEqual('', entry[1][0][1])
559
# We should have a real entry.
560
self.assertNotEqual((None, None), entry)
561
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
562
# We should have gotten a real sha1
563
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
566
# The dirblock has been updated
567
self.assertEqual(sha1sum, entry[1][0][1])
568
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
569
state._dirblock_state)
572
# Now, since we are the only one holding a lock, we should be able
573
# to save and have it written to disk
578
# Re-open the file, and ensure that the state has been updated.
579
state = dirstate.DirState.on_file('dirstate')
582
entry = state._get_entry(0, path_utf8='a-file')
583
self.assertEqual(sha1sum, entry[1][0][1])
587
def test_save_fails_quietly_if_locked(self):
588
"""If dirstate is locked, save will fail without complaining."""
589
self.build_tree(['a-file'])
590
state = dirstate.DirState.initialize('dirstate')
592
# No stat and no sha1 sum.
593
state.add('a-file', 'a-file-id', 'file', None, '')
598
state = dirstate.DirState.on_file('dirstate')
601
entry = state._get_entry(0, path_utf8='a-file')
602
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
603
# We should have gotten a real sha1
604
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
606
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
607
state._dirblock_state)
609
# Now, before we try to save, grab another dirstate, and take out a
611
# TODO: jam 20070315 Ideally this would be locked by another
612
# process. To make sure the file is really OS locked.
613
state2 = dirstate.DirState.on_file('dirstate')
616
# This won't actually write anything, because it couldn't grab
617
# a write lock. But it shouldn't raise an error, either.
618
# TODO: jam 20070315 We should probably distinguish between
619
# being dirty because of 'update_entry'. And dirty
620
# because of real modification. So that save() *does*
621
# raise a real error if it fails when we have real
629
# The file on disk should not be modified.
630
state = dirstate.DirState.on_file('dirstate')
633
entry = state._get_entry(0, path_utf8='a-file')
634
self.assertEqual('', entry[1][0][1])
639
class TestDirStateInitialize(TestCaseWithDirState):
641
def test_initialize(self):
642
expected_result = ([], [
643
(('', '', 'TREE_ROOT'), # common details
644
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
647
state = dirstate.DirState.initialize('dirstate')
649
self.assertIsInstance(state, dirstate.DirState)
650
lines = state.get_lines()
651
self.assertFileEqual(''.join(state.get_lines()),
653
self.check_state_with_reopen(expected_result, state)
659
class TestDirStateManipulations(TestCaseWithDirState):
661
def test_set_state_from_inventory_no_content_no_parents(self):
662
# setting the current inventory is a slow but important api to support.
663
tree1 = self.make_branch_and_memory_tree('tree1')
667
revid1 = tree1.commit('foo').encode('utf8')
668
root_id = tree1.inventory.root.file_id
669
inv = tree1.inventory
672
expected_result = [], [
673
(('', '', root_id), [
674
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
675
state = dirstate.DirState.initialize('dirstate')
677
state.set_state_from_inventory(inv)
678
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
680
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
681
state._dirblock_state)
686
# This will unlock it
687
self.check_state_with_reopen(expected_result, state)
689
def test_set_path_id_no_parents(self):
690
"""The id of a path can be changed trivally with no parents."""
691
state = dirstate.DirState.initialize('dirstate')
693
# check precondition to be sure the state does change appropriately.
695
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
696
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
697
list(state._iter_entries()))
698
state.set_path_id('', 'foobarbaz')
700
(('', '', 'foobarbaz'), [('d', '', 0, False,
701
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
702
self.assertEqual(expected_rows, list(state._iter_entries()))
703
# should work across save too
707
state = dirstate.DirState.on_file('dirstate')
711
self.assertEqual(expected_rows, list(state._iter_entries()))
715
def test_set_path_id_with_parents(self):
716
"""Set the root file id in a dirstate with parents"""
717
mt = self.make_branch_and_tree('mt')
718
# in case the default tree format uses a different root id
719
mt.set_root_id('TREE_ROOT')
720
mt.commit('foo', rev_id='parent-revid')
721
rt = mt.branch.repository.revision_tree('parent-revid')
722
state = dirstate.DirState.initialize('dirstate')
725
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
726
state.set_path_id('', 'foobarbaz')
728
# now see that it is what we expected
730
(('', '', 'TREE_ROOT'),
731
[('a', '', 0, False, ''),
732
('d', '', 0, False, 'parent-revid'),
734
(('', '', 'foobarbaz'),
735
[('d', '', 0, False, ''),
736
('a', '', 0, False, ''),
740
self.assertEqual(expected_rows, list(state._iter_entries()))
741
# should work across save too
745
# now flush & check we get the same
746
state = dirstate.DirState.on_file('dirstate')
750
self.assertEqual(expected_rows, list(state._iter_entries()))
753
# now change within an existing file-backed state
757
state.set_path_id('', 'tree-root-2')
763
def test_set_parent_trees_no_content(self):
764
# set_parent_trees is a slow but important api to support.
765
tree1 = self.make_branch_and_memory_tree('tree1')
769
revid1 = tree1.commit('foo')
772
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
773
tree2 = MemoryTree.create_on_branch(branch2)
776
revid2 = tree2.commit('foo')
777
root_id = tree2.inventory.root.file_id
780
state = dirstate.DirState.initialize('dirstate')
782
state.set_path_id('', root_id)
783
state.set_parent_trees(
784
((revid1, tree1.branch.repository.revision_tree(revid1)),
785
(revid2, tree2.branch.repository.revision_tree(revid2)),
786
('ghost-rev', None)),
788
# check we can reopen and use the dirstate after setting parent
795
state = dirstate.DirState.on_file('dirstate')
798
self.assertEqual([revid1, revid2, 'ghost-rev'],
799
state.get_parent_ids())
800
# iterating the entire state ensures that the state is parsable.
801
list(state._iter_entries())
802
# be sure that it sets not appends - change it
803
state.set_parent_trees(
804
((revid1, tree1.branch.repository.revision_tree(revid1)),
805
('ghost-rev', None)),
807
# and now put it back.
808
state.set_parent_trees(
809
((revid1, tree1.branch.repository.revision_tree(revid1)),
810
(revid2, tree2.branch.repository.revision_tree(revid2)),
811
('ghost-rev', tree2.branch.repository.revision_tree(None))),
813
self.assertEqual([revid1, revid2, 'ghost-rev'],
814
state.get_parent_ids())
815
# the ghost should be recorded as such by set_parent_trees.
816
self.assertEqual(['ghost-rev'], state.get_ghosts())
818
[(('', '', root_id), [
819
('d', '', 0, False, dirstate.DirState.NULLSTAT),
820
('d', '', 0, False, revid1),
821
('d', '', 0, False, revid2)
823
list(state._iter_entries()))
827
def test_set_parent_trees_file_missing_from_tree(self):
828
# Adding a parent tree may reference files not in the current state.
829
# they should get listed just once by id, even if they are in two
831
# set_parent_trees is a slow but important api to support.
832
tree1 = self.make_branch_and_memory_tree('tree1')
836
tree1.add(['a file'], ['file-id'], ['file'])
837
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
838
revid1 = tree1.commit('foo')
841
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
842
tree2 = MemoryTree.create_on_branch(branch2)
845
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
846
revid2 = tree2.commit('foo')
847
root_id = tree2.inventory.root.file_id
850
# check the layout in memory
851
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
852
(('', '', root_id), [
853
('d', '', 0, False, dirstate.DirState.NULLSTAT),
854
('d', '', 0, False, revid1.encode('utf8')),
855
('d', '', 0, False, revid2.encode('utf8'))
857
(('', 'a file', 'file-id'), [
858
('a', '', 0, False, ''),
859
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
860
revid1.encode('utf8')),
861
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
862
revid2.encode('utf8'))
865
state = dirstate.DirState.initialize('dirstate')
867
state.set_path_id('', root_id)
868
state.set_parent_trees(
869
((revid1, tree1.branch.repository.revision_tree(revid1)),
870
(revid2, tree2.branch.repository.revision_tree(revid2)),
876
# check_state_with_reopen will unlock
877
self.check_state_with_reopen(expected_result, state)
879
### add a path via _set_data - so we dont need delta work, just
880
# raw data in, and ensure that it comes out via get_lines happily.
882
def test_add_path_to_root_no_parents_all_data(self):
883
# The most trivial addition of a path is when there are no parents and
884
# its in the root and all data about the file is supplied
885
self.build_tree(['a file'])
886
stat = os.lstat('a file')
887
# the 1*20 is the sha1 pretend value.
888
state = dirstate.DirState.initialize('dirstate')
890
(('', '', 'TREE_ROOT'), [
891
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
893
(('', 'a file', 'a file id'), [
894
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
898
state.add('a file', 'a file id', 'file', stat, '1'*20)
899
# having added it, it should be in the output of iter_entries.
900
self.assertEqual(expected_entries, list(state._iter_entries()))
901
# saving and reloading should not affect this.
905
state = dirstate.DirState.on_file('dirstate')
908
self.assertEqual(expected_entries, list(state._iter_entries()))
912
def test_add_path_to_unversioned_directory(self):
913
"""Adding a path to an unversioned directory should error.
915
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
916
once dirstate is stable and if it is merged with WorkingTree3, consider
917
removing this copy of the test.
919
self.build_tree(['unversioned/', 'unversioned/a file'])
920
state = dirstate.DirState.initialize('dirstate')
922
self.assertRaises(errors.NotVersionedError, state.add,
923
'unversioned/a file', 'a file id', 'file', None, None)
927
def test_add_directory_to_root_no_parents_all_data(self):
928
# The most trivial addition of a dir is when there are no parents and
929
# its in the root and all data about the file is supplied
930
self.build_tree(['a dir/'])
931
stat = os.lstat('a dir')
933
(('', '', 'TREE_ROOT'), [
934
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
936
(('', 'a dir', 'a dir id'), [
937
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
940
state = dirstate.DirState.initialize('dirstate')
942
state.add('a dir', 'a dir id', 'directory', stat, None)
943
# having added it, it should be in the output of iter_entries.
944
self.assertEqual(expected_entries, list(state._iter_entries()))
945
# saving and reloading should not affect this.
949
state = dirstate.DirState.on_file('dirstate')
953
self.assertEqual(expected_entries, list(state._iter_entries()))
957
def test_add_symlink_to_root_no_parents_all_data(self):
958
# The most trivial addition of a symlink when there are no parents and
959
# its in the root and all data about the file is supplied
960
# bzr doesn't support fake symlinks on windows, yet.
961
if not has_symlinks():
962
raise TestSkipped("No symlink support")
963
os.symlink('target', 'a link')
964
stat = os.lstat('a link')
966
(('', '', 'TREE_ROOT'), [
967
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
969
(('', 'a link', 'a link id'), [
970
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
973
state = dirstate.DirState.initialize('dirstate')
975
state.add('a link', 'a link id', 'symlink', stat, 'target')
976
# having added it, it should be in the output of iter_entries.
977
self.assertEqual(expected_entries, list(state._iter_entries()))
978
# saving and reloading should not affect this.
982
state = dirstate.DirState.on_file('dirstate')
985
self.assertEqual(expected_entries, list(state._iter_entries()))
989
def test_add_directory_and_child_no_parents_all_data(self):
990
# after adding a directory, we should be able to add children to it.
991
self.build_tree(['a dir/', 'a dir/a file'])
992
dirstat = os.lstat('a dir')
993
filestat = os.lstat('a dir/a file')
995
(('', '', 'TREE_ROOT'), [
996
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
998
(('', 'a dir', 'a dir id'), [
999
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1001
(('a dir', 'a file', 'a file id'), [
1002
('f', '1'*20, 25, False,
1003
dirstate.pack_stat(filestat)), # current tree details
1006
state = dirstate.DirState.initialize('dirstate')
1008
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1009
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
1010
# added it, it should be in the output of iter_entries.
1011
self.assertEqual(expected_entries, list(state._iter_entries()))
1012
# saving and reloading should not affect this.
1016
state = dirstate.DirState.on_file('dirstate')
1019
self.assertEqual(expected_entries, list(state._iter_entries()))
1023
def test_add_tree_reference(self):
1024
# make a dirstate and add a tree reference
1025
state = dirstate.DirState.initialize('dirstate')
1027
('', 'subdir', 'subdir-id'),
1028
[('t', 'subtree-123123', 0, False,
1029
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1032
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1033
entry = state._get_entry(0, 'subdir-id', 'subdir')
1034
self.assertEqual(entry, expected_entry)
1039
# now check we can read it back
1043
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1044
self.assertEqual(entry, entry2)
1045
self.assertEqual(entry, expected_entry)
1046
# and lookup by id should work too
1047
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1048
self.assertEqual(entry, expected_entry)
1052
def test_add_forbidden_names(self):
1053
state = dirstate.DirState.initialize('dirstate')
1054
self.addCleanup(state.unlock)
1055
self.assertRaises(errors.BzrError,
1056
state.add, '.', 'ass-id', 'directory', None, None)
1057
self.assertRaises(errors.BzrError,
1058
state.add, '..', 'ass-id', 'directory', None, None)
1061
class TestGetLines(TestCaseWithDirState):
1063
def test_get_line_with_2_rows(self):
1064
state = self.create_dirstate_with_root_and_subdir()
1066
self.assertEqual(['#bazaar dirstate flat format 3\n',
1067
'crc32: 41262208\n',
1071
'\x00\x00a-root-value\x00'
1072
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1073
'\x00subdir\x00subdir-id\x00'
1074
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1075
], state.get_lines())
1079
def test_entry_to_line(self):
1080
state = self.create_dirstate_with_root()
1083
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1084
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1085
state._entry_to_line(state._dirblocks[0][1][0]))
1089
def test_entry_to_line_with_parent(self):
1090
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1091
root_entry = ('', '', 'a-root-value'), [
1092
('d', '', 0, False, packed_stat), # current tree details
1093
# first: a pointer to the current location
1094
('a', 'dirname/basename', 0, False, ''),
1096
state = dirstate.DirState.initialize('dirstate')
1099
'\x00\x00a-root-value\x00'
1100
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1101
'a\x00dirname/basename\x000\x00n\x00',
1102
state._entry_to_line(root_entry))
1106
def test_entry_to_line_with_two_parents_at_different_paths(self):
1107
# / in the tree, at / in one parent and /dirname/basename in the other.
1108
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1109
root_entry = ('', '', 'a-root-value'), [
1110
('d', '', 0, False, packed_stat), # current tree details
1111
('d', '', 0, False, 'rev_id'), # first parent details
1112
# second: a pointer to the current location
1113
('a', 'dirname/basename', 0, False, ''),
1115
state = dirstate.DirState.initialize('dirstate')
1118
'\x00\x00a-root-value\x00'
1119
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1120
'd\x00\x000\x00n\x00rev_id\x00'
1121
'a\x00dirname/basename\x000\x00n\x00',
1122
state._entry_to_line(root_entry))
1126
def test_iter_entries(self):
1127
# we should be able to iterate the dirstate entries from end to end
1128
# this is for get_lines to be easy to read.
1129
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1131
root_entries = [(('', '', 'a-root-value'), [
1132
('d', '', 0, False, packed_stat), # current tree details
1134
dirblocks.append(('', root_entries))
1135
# add two files in the root
1136
subdir_entry = ('', 'subdir', 'subdir-id'), [
1137
('d', '', 0, False, packed_stat), # current tree details
1139
afile_entry = ('', 'afile', 'afile-id'), [
1140
('f', 'sha1value', 34, False, packed_stat), # current tree details
1142
dirblocks.append(('', [subdir_entry, afile_entry]))
1144
file_entry2 = ('subdir', '2file', '2file-id'), [
1145
('f', 'sha1value', 23, False, packed_stat), # current tree details
1147
dirblocks.append(('subdir', [file_entry2]))
1148
state = dirstate.DirState.initialize('dirstate')
1150
state._set_data([], dirblocks)
1151
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1153
self.assertEqual(expected_entries, list(state._iter_entries()))
1158
class TestGetBlockRowIndex(TestCaseWithDirState):
1160
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1161
file_present, state, dirname, basename, tree_index):
1162
self.assertEqual((block_index, row_index, dir_present, file_present),
1163
state._get_block_entry_index(dirname, basename, tree_index))
1165
block = state._dirblocks[block_index]
1166
self.assertEqual(dirname, block[0])
1167
if dir_present and file_present:
1168
row = state._dirblocks[block_index][1][row_index]
1169
self.assertEqual(dirname, row[0][0])
1170
self.assertEqual(basename, row[0][1])
1172
def test_simple_structure(self):
1173
state = self.create_dirstate_with_root_and_subdir()
1174
self.addCleanup(state.unlock)
1175
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1176
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1177
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1178
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1179
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1182
def test_complex_structure_exists(self):
1183
state = self.create_complex_dirstate()
1184
self.addCleanup(state.unlock)
1185
# Make sure we can find everything that exists
1186
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1187
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1188
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1189
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1190
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1191
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1192
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1193
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1194
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1195
'b', 'h\xc3\xa5', 0)
1197
def test_complex_structure_missing(self):
1198
state = self.create_complex_dirstate()
1199
self.addCleanup(state.unlock)
1200
# Make sure things would be inserted in the right locations
1201
# '_' comes before 'a'
1202
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1203
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1204
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1205
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1207
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1208
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1209
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1210
# This would be inserted between a/ and b/
1211
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1213
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1216
class TestGetEntry(TestCaseWithDirState):
1218
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1219
"""Check that the right entry is returned for a request to getEntry."""
1220
entry = state._get_entry(index, path_utf8=path)
1222
self.assertEqual((None, None), entry)
1225
self.assertEqual((dirname, basename, file_id), cur[:3])
1227
def test_simple_structure(self):
1228
state = self.create_dirstate_with_root_and_subdir()
1229
self.addCleanup(state.unlock)
1230
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1231
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1232
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1233
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1234
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1236
def test_complex_structure_exists(self):
1237
state = self.create_complex_dirstate()
1238
self.addCleanup(state.unlock)
1239
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1240
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1241
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1242
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1243
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1244
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1245
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1246
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1247
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1250
def test_complex_structure_missing(self):
1251
state = self.create_complex_dirstate()
1252
self.addCleanup(state.unlock)
1253
self.assertEntryEqual(None, None, None, state, '_', 0)
1254
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1255
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1256
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1258
def test_get_entry_uninitialized(self):
1259
"""Calling get_entry will load data if it needs to"""
1260
state = self.create_dirstate_with_root()
1266
state = dirstate.DirState.on_file('dirstate')
1269
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1270
state._header_state)
1271
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1272
state._dirblock_state)
1273
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1278
class TestDirstateSortOrder(TestCaseWithTransport):
1279
"""Test that DirState adds entries in the right order."""
1281
def test_add_sorting(self):
1282
"""Add entries in lexicographical order, we get path sorted order.
1284
This tests it to a depth of 4, to make sure we don't just get it right
1285
at a single depth. 'a/a' should come before 'a-a', even though it
1286
doesn't lexicographically.
1288
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1289
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1292
state = dirstate.DirState.initialize('dirstate')
1293
self.addCleanup(state.unlock)
1295
fake_stat = os.stat('dirstate')
1297
d_id = d.replace('/', '_')+'-id'
1298
file_path = d + '/f'
1299
file_id = file_path.replace('/', '_')+'-id'
1300
state.add(d, d_id, 'directory', fake_stat, null_sha)
1301
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1303
expected = ['', '', 'a',
1304
'a/a', 'a/a/a', 'a/a/a/a',
1305
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1307
split = lambda p:p.split('/')
1308
self.assertEqual(sorted(expected, key=split), expected)
1309
dirblock_names = [d[0] for d in state._dirblocks]
1310
self.assertEqual(expected, dirblock_names)
1312
def test_set_parent_trees_correct_order(self):
1313
"""After calling set_parent_trees() we should maintain the order."""
1314
dirs = ['a', 'a-a', 'a/a']
1316
state = dirstate.DirState.initialize('dirstate')
1317
self.addCleanup(state.unlock)
1319
fake_stat = os.stat('dirstate')
1321
d_id = d.replace('/', '_')+'-id'
1322
file_path = d + '/f'
1323
file_id = file_path.replace('/', '_')+'-id'
1324
state.add(d, d_id, 'directory', fake_stat, null_sha)
1325
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1327
expected = ['', '', 'a', 'a/a', 'a-a']
1328
dirblock_names = [d[0] for d in state._dirblocks]
1329
self.assertEqual(expected, dirblock_names)
1331
# *really* cheesy way to just get an empty tree
1332
repo = self.make_repository('repo')
1333
empty_tree = repo.revision_tree(None)
1334
state.set_parent_trees([('null:', empty_tree)], [])
1336
dirblock_names = [d[0] for d in state._dirblocks]
1337
self.assertEqual(expected, dirblock_names)
1340
class InstrumentedDirState(dirstate.DirState):
1341
"""An DirState with instrumented sha1 functionality."""
1343
def __init__(self, path):
1344
super(InstrumentedDirState, self).__init__(path)
1345
self._time_offset = 0
1348
def _sha_cutoff_time(self):
1349
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1350
self._cutoff_time = timestamp + self._time_offset
1352
def _sha1_file(self, abspath, entry):
1353
self._log.append(('sha1', abspath))
1354
return super(InstrumentedDirState, self)._sha1_file(abspath, entry)
1356
def _read_link(self, abspath, old_link):
1357
self._log.append(('read_link', abspath, old_link))
1358
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1360
def _lstat(self, abspath, entry):
1361
self._log.append(('lstat', abspath))
1362
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1364
def _is_executable(self, mode, old_executable):
1365
self._log.append(('is_exec', mode, old_executable))
1366
return super(InstrumentedDirState, self)._is_executable(mode,
1369
def adjust_time(self, secs):
1370
"""Move the clock forward or back.
1372
:param secs: The amount to adjust the clock by. Positive values make it
1373
seem as if we are in the future, negative values make it seem like we
1376
self._time_offset += secs
1377
self._cutoff_time = None
1380
class _FakeStat(object):
1381
"""A class with the same attributes as a real stat result."""
1383
def __init__(self, size, mtime, ctime, dev, ino, mode):
1385
self.st_mtime = mtime
1386
self.st_ctime = ctime
1392
class TestUpdateEntry(TestCaseWithDirState):
1393
"""Test the DirState.update_entry functions"""
1395
def get_state_with_a(self):
1396
"""Create a DirState tracking a single object named 'a'"""
1397
state = InstrumentedDirState.initialize('dirstate')
1398
self.addCleanup(state.unlock)
1399
state.add('a', 'a-id', 'file', None, '')
1400
entry = state._get_entry(0, path_utf8='a')
1403
def test_update_entry(self):
1404
state, entry = self.get_state_with_a()
1405
self.build_tree(['a'])
1406
# Add one where we don't provide the stat or sha already
1407
self.assertEqual(('', 'a', 'a-id'), entry[0])
1408
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1410
# Flush the buffers to disk
1412
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1413
state._dirblock_state)
1415
stat_value = os.lstat('a')
1416
packed_stat = dirstate.pack_stat(stat_value)
1417
link_or_sha1 = state.update_entry(entry, abspath='a',
1418
stat_value=stat_value)
1419
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1422
# The dirblock entry should be updated with the new info
1423
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1425
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1426
state._dirblock_state)
1427
mode = stat_value.st_mode
1428
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1431
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1432
state._dirblock_state)
1434
# If we do it again right away, we don't know if the file has changed
1435
# so we will re-read the file. Roll the clock back so the file is
1436
# guaranteed to look too new.
1437
state.adjust_time(-10)
1439
link_or_sha1 = state.update_entry(entry, abspath='a',
1440
stat_value=stat_value)
1441
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1442
('sha1', 'a'), ('is_exec', mode, False),
1444
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1446
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1447
state._dirblock_state)
1450
# However, if we move the clock forward so the file is considered
1451
# "stable", it should just returned the cached value.
1452
state.adjust_time(20)
1453
link_or_sha1 = state.update_entry(entry, abspath='a',
1454
stat_value=stat_value)
1455
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1457
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1458
('sha1', 'a'), ('is_exec', mode, False),
1461
def test_update_entry_no_stat_value(self):
1462
"""Passing the stat_value is optional."""
1463
state, entry = self.get_state_with_a()
1464
state.adjust_time(-10) # Make sure the file looks new
1465
self.build_tree(['a'])
1466
# Add one where we don't provide the stat or sha already
1467
link_or_sha1 = state.update_entry(entry, abspath='a')
1468
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1470
stat_value = os.lstat('a')
1471
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1472
('is_exec', stat_value.st_mode, False),
1475
def test_update_entry_symlink(self):
1476
"""Update entry should read symlinks."""
1477
if not osutils.has_symlinks():
1478
# PlatformDeficiency / TestSkipped
1479
raise TestSkipped("No symlink support")
1480
state, entry = self.get_state_with_a()
1482
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1483
state._dirblock_state)
1484
os.symlink('target', 'a')
1486
state.adjust_time(-10) # Make the symlink look new
1487
stat_value = os.lstat('a')
1488
packed_stat = dirstate.pack_stat(stat_value)
1489
link_or_sha1 = state.update_entry(entry, abspath='a',
1490
stat_value=stat_value)
1491
self.assertEqual('target', link_or_sha1)
1492
self.assertEqual([('read_link', 'a', '')], state._log)
1493
# Dirblock is updated
1494
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1496
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1497
state._dirblock_state)
1499
# Because the stat_value looks new, we should re-read the target
1500
link_or_sha1 = state.update_entry(entry, abspath='a',
1501
stat_value=stat_value)
1502
self.assertEqual('target', link_or_sha1)
1503
self.assertEqual([('read_link', 'a', ''),
1504
('read_link', 'a', 'target'),
1506
state.adjust_time(+20) # Skip into the future, all files look old
1507
link_or_sha1 = state.update_entry(entry, abspath='a',
1508
stat_value=stat_value)
1509
self.assertEqual('target', link_or_sha1)
1510
# There should not be a new read_link call.
1511
# (this is a weak assertion, because read_link is fairly inexpensive,
1512
# versus the number of symlinks that we would have)
1513
self.assertEqual([('read_link', 'a', ''),
1514
('read_link', 'a', 'target'),
1517
def test_update_entry_dir(self):
1518
state, entry = self.get_state_with_a()
1519
self.build_tree(['a/'])
1520
self.assertIs(None, state.update_entry(entry, 'a'))
1522
def create_and_test_file(self, state, entry):
1523
"""Create a file at 'a' and verify the state finds it.
1525
The state should already be versioning *something* at 'a'. This makes
1526
sure that state.update_entry recognizes it as a file.
1528
self.build_tree(['a'])
1529
stat_value = os.lstat('a')
1530
packed_stat = dirstate.pack_stat(stat_value)
1532
link_or_sha1 = state.update_entry(entry, abspath='a')
1533
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1535
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1539
def create_and_test_dir(self, state, entry):
1540
"""Create a directory at 'a' and verify the state finds it.
1542
The state should already be versioning *something* at 'a'. This makes
1543
sure that state.update_entry recognizes it as a directory.
1545
self.build_tree(['a/'])
1546
stat_value = os.lstat('a')
1547
packed_stat = dirstate.pack_stat(stat_value)
1549
link_or_sha1 = state.update_entry(entry, abspath='a')
1550
self.assertIs(None, link_or_sha1)
1551
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1555
def create_and_test_symlink(self, state, entry):
1556
"""Create a symlink at 'a' and verify the state finds it.
1558
The state should already be versioning *something* at 'a'. This makes
1559
sure that state.update_entry recognizes it as a symlink.
1561
This should not be called if this platform does not have symlink
1564
# caller should care about skipping test on platforms without symlinks
1565
os.symlink('path/to/foo', 'a')
1567
stat_value = os.lstat('a')
1568
packed_stat = dirstate.pack_stat(stat_value)
1570
link_or_sha1 = state.update_entry(entry, abspath='a')
1571
self.assertEqual('path/to/foo', link_or_sha1)
1572
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1576
def test_update_missing_file(self):
1577
state, entry = self.get_state_with_a()
1578
packed_stat = self.create_and_test_file(state, entry)
1579
# Now if we delete the file, update_entry should recover and
1582
self.assertIs(None, state.update_entry(entry, abspath='a'))
1583
# And the record shouldn't be changed.
1584
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1585
self.assertEqual([('f', digest, 14, False, packed_stat)],
1588
def test_update_missing_dir(self):
1589
state, entry = self.get_state_with_a()
1590
packed_stat = self.create_and_test_dir(state, entry)
1591
# Now if we delete the directory, update_entry should recover and
1594
self.assertIs(None, state.update_entry(entry, abspath='a'))
1595
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1597
def test_update_missing_symlink(self):
1598
if not osutils.has_symlinks():
1599
# PlatformDeficiency / TestSkipped
1600
raise TestSkipped("No symlink support")
1601
state, entry = self.get_state_with_a()
1602
packed_stat = self.create_and_test_symlink(state, entry)
1604
self.assertIs(None, state.update_entry(entry, abspath='a'))
1605
# And the record shouldn't be changed.
1606
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1609
def test_update_file_to_dir(self):
1610
"""If a file changes to a directory we return None for the sha.
1611
We also update the inventory record.
1613
state, entry = self.get_state_with_a()
1614
self.create_and_test_file(state, entry)
1616
self.create_and_test_dir(state, entry)
1618
def test_update_file_to_symlink(self):
1619
"""File becomes a symlink"""
1620
if not osutils.has_symlinks():
1621
# PlatformDeficiency / TestSkipped
1622
raise TestSkipped("No symlink support")
1623
state, entry = self.get_state_with_a()
1624
self.create_and_test_file(state, entry)
1626
self.create_and_test_symlink(state, entry)
1628
def test_update_dir_to_file(self):
1629
"""Directory becoming a file updates the entry."""
1630
state, entry = self.get_state_with_a()
1631
self.create_and_test_dir(state, entry)
1633
self.create_and_test_file(state, entry)
1635
def test_update_dir_to_symlink(self):
1636
"""Directory becomes a symlink"""
1637
if not osutils.has_symlinks():
1638
# PlatformDeficiency / TestSkipped
1639
raise TestSkipped("No symlink support")
1640
state, entry = self.get_state_with_a()
1641
self.create_and_test_dir(state, entry)
1643
self.create_and_test_symlink(state, entry)
1645
def test_update_symlink_to_file(self):
1646
"""Symlink becomes a file"""
1647
if not has_symlinks():
1648
raise TestSkipped("No symlink support")
1649
state, entry = self.get_state_with_a()
1650
self.create_and_test_symlink(state, entry)
1652
self.create_and_test_file(state, entry)
1654
def test_update_symlink_to_dir(self):
1655
"""Symlink becomes a directory"""
1656
if not has_symlinks():
1657
raise TestSkipped("No symlink support")
1658
state, entry = self.get_state_with_a()
1659
self.create_and_test_symlink(state, entry)
1661
self.create_and_test_dir(state, entry)
1663
def test__is_executable_win32(self):
1664
state, entry = self.get_state_with_a()
1665
self.build_tree(['a'])
1667
# Make sure we are using the win32 implementation of _is_executable
1668
state._is_executable = state._is_executable_win32
1670
# The file on disk is not executable, but we are marking it as though
1671
# it is. With _is_executable_win32 we ignore what is on disk.
1672
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1674
stat_value = os.lstat('a')
1675
packed_stat = dirstate.pack_stat(stat_value)
1677
state.adjust_time(-10) # Make sure everything is new
1678
# Make sure it wants to kkkkkkkk
1679
state.update_entry(entry, abspath='a', stat_value=stat_value)
1681
# The row is updated, but the executable bit stays set.
1682
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1683
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1686
class TestPackStat(TestCaseWithTransport):
1688
def assertPackStat(self, expected, stat_value):
1689
"""Check the packed and serialized form of a stat value."""
1690
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1692
def test_pack_stat_int(self):
1693
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1694
# Make sure that all parameters have an impact on the packed stat.
1695
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1698
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1699
st.st_mtime = 1172758620
1701
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1702
st.st_ctime = 1172758630
1704
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1707
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1708
st.st_ino = 6499540L
1710
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1711
st.st_mode = 0100744
1713
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1715
def test_pack_stat_float(self):
1716
"""On some platforms mtime and ctime are floats.
1718
Make sure we don't get warnings or errors, and that we ignore changes <
1721
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1722
777L, 6499538L, 0100644)
1723
# These should all be the same as the integer counterparts
1724
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1725
st.st_mtime = 1172758620.0
1727
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1728
st.st_ctime = 1172758630.0
1730
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1731
# fractional seconds are discarded, so no change from above
1732
st.st_mtime = 1172758620.453
1733
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1734
st.st_ctime = 1172758630.228
1735
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1738
class TestBisect(TestCaseWithDirState):
1739
"""Test the ability to bisect into the disk format."""
1742
def assertBisect(self, expected_map, map_keys, state, paths):
1743
"""Assert that bisecting for paths returns the right result.
1745
:param expected_map: A map from key => entry value
1746
:param map_keys: The keys to expect for each path
1747
:param state: The DirState object.
1748
:param paths: A list of paths, these will automatically be split into
1749
(dir, name) tuples, and sorted according to how _bisect
1752
dir_names = sorted(osutils.split(p) for p in paths)
1753
result = state._bisect(dir_names)
1754
# For now, results are just returned in whatever order we read them.
1755
# We could sort by (dir, name, file_id) or something like that, but in
1756
# the end it would still be fairly arbitrary, and we don't want the
1757
# extra overhead if we can avoid it. So sort everything to make sure
1759
assert len(map_keys) == len(dir_names)
1761
for dir_name, keys in zip(dir_names, map_keys):
1763
# This should not be present in the output
1765
expected[dir_name] = sorted(expected_map[k] for k in keys)
1767
for dir_name in result:
1768
result[dir_name].sort()
1770
self.assertEqual(expected, result)
1772
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1773
"""Assert that bisecting for dirbblocks returns the right result.
1775
:param expected_map: A map from key => expected values
1776
:param map_keys: A nested list of paths we expect to be returned.
1777
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1778
:param state: The DirState object.
1779
:param paths: A list of directories
1781
result = state._bisect_dirblocks(paths)
1782
assert len(map_keys) == len(paths)
1785
for path, keys in zip(paths, map_keys):
1787
# This should not be present in the output
1789
expected[path] = sorted(expected_map[k] for k in keys)
1793
self.assertEqual(expected, result)
1795
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1796
"""Assert the return value of a recursive bisection.
1798
:param expected_map: A map from key => entry value
1799
:param map_keys: A list of paths we expect to be returned.
1800
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1801
:param state: The DirState object.
1802
:param paths: A list of files and directories. It will be broken up
1803
into (dir, name) pairs and sorted before calling _bisect_recursive.
1806
for key in map_keys:
1807
entry = expected_map[key]
1808
dir_name_id, trees_info = entry
1809
expected[dir_name_id] = trees_info
1811
dir_names = sorted(osutils.split(p) for p in paths)
1812
result = state._bisect_recursive(dir_names)
1814
self.assertEqual(expected, result)
1816
def test_bisect_each(self):
1817
"""Find a single record using bisect."""
1818
tree, state, expected = self.create_basic_dirstate()
1820
# Bisect should return the rows for the specified files.
1821
self.assertBisect(expected, [['']], state, [''])
1822
self.assertBisect(expected, [['a']], state, ['a'])
1823
self.assertBisect(expected, [['b']], state, ['b'])
1824
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1825
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1826
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1827
self.assertBisect(expected, [['f']], state, ['f'])
1829
def test_bisect_multi(self):
1830
"""Bisect can be used to find multiple records at the same time."""
1831
tree, state, expected = self.create_basic_dirstate()
1832
# Bisect should be capable of finding multiple entries at the same time
1833
self.assertBisect(expected, [['a'], ['b'], ['f']],
1834
state, ['a', 'b', 'f'])
1835
# ('', 'f') sorts before the others
1836
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1837
state, ['b/d', 'b/d/e', 'f'])
1839
def test_bisect_one_page(self):
1840
"""Test bisect when there is only 1 page to read"""
1841
tree, state, expected = self.create_basic_dirstate()
1842
state._bisect_page_size = 5000
1843
self.assertBisect(expected,[['']], state, [''])
1844
self.assertBisect(expected,[['a']], state, ['a'])
1845
self.assertBisect(expected,[['b']], state, ['b'])
1846
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1847
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1848
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1849
self.assertBisect(expected,[['f']], state, ['f'])
1850
self.assertBisect(expected,[['a'], ['b'], ['f']],
1851
state, ['a', 'b', 'f'])
1852
# ('', 'f') sorts before the others
1853
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1854
state, ['b/d', 'b/d/e', 'f'])
1856
def test_bisect_duplicate_paths(self):
1857
"""When bisecting for a path, handle multiple entries."""
1858
tree, state, expected = self.create_duplicated_dirstate()
1860
# Now make sure that both records are properly returned.
1861
self.assertBisect(expected, [['']], state, [''])
1862
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1863
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1864
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1865
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1866
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1868
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1870
def test_bisect_page_size_too_small(self):
1871
"""If the page size is too small, we will auto increase it."""
1872
tree, state, expected = self.create_basic_dirstate()
1873
state._bisect_page_size = 50
1874
self.assertBisect(expected, [None], state, ['b/e'])
1875
self.assertBisect(expected, [['a']], state, ['a'])
1876
self.assertBisect(expected, [['b']], state, ['b'])
1877
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1878
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1879
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1880
self.assertBisect(expected, [['f']], state, ['f'])
1882
def test_bisect_missing(self):
1883
"""Test that bisect return None if it cannot find a path."""
1884
tree, state, expected = self.create_basic_dirstate()
1885
self.assertBisect(expected, [None], state, ['foo'])
1886
self.assertBisect(expected, [None], state, ['b/foo'])
1887
self.assertBisect(expected, [None], state, ['bar/foo'])
1889
self.assertBisect(expected, [['a'], None, ['b/d']],
1890
state, ['a', 'foo', 'b/d'])
1892
def test_bisect_rename(self):
1893
"""Check that we find a renamed row."""
1894
tree, state, expected = self.create_renamed_dirstate()
1896
# Search for the pre and post renamed entries
1897
self.assertBisect(expected, [['a']], state, ['a'])
1898
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1899
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1900
self.assertBisect(expected, [['h']], state, ['h'])
1902
# What about b/d/e? shouldn't that also get 2 directory entries?
1903
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1904
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1906
def test_bisect_dirblocks(self):
1907
tree, state, expected = self.create_duplicated_dirstate()
1908
self.assertBisectDirBlocks(expected,
1909
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
1910
self.assertBisectDirBlocks(expected,
1911
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1912
self.assertBisectDirBlocks(expected,
1913
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1914
self.assertBisectDirBlocks(expected,
1915
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
1916
['b/c', 'b/c2', 'b/d', 'b/d2'],
1917
['b/d/e', 'b/d/e2'],
1918
], state, ['', 'b', 'b/d'])
1920
def test_bisect_dirblocks_missing(self):
1921
tree, state, expected = self.create_basic_dirstate()
1922
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1923
state, ['b/d', 'b/e'])
1924
# Files don't show up in this search
1925
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1926
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1927
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1928
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1929
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1931
def test_bisect_recursive_each(self):
1932
tree, state, expected = self.create_basic_dirstate()
1933
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1934
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1935
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1936
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1938
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1940
self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
1944
def test_bisect_recursive_multiple(self):
1945
tree, state, expected = self.create_basic_dirstate()
1946
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1947
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1948
state, ['b/d', 'b/d/e'])
1950
def test_bisect_recursive_missing(self):
1951
tree, state, expected = self.create_basic_dirstate()
1952
self.assertBisectRecursive(expected, [], state, ['d'])
1953
self.assertBisectRecursive(expected, [], state, ['b/e'])
1954
self.assertBisectRecursive(expected, [], state, ['g'])
1955
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1957
def test_bisect_recursive_renamed(self):
1958
tree, state, expected = self.create_renamed_dirstate()
1960
# Looking for either renamed item should find the other
1961
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1962
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1963
# Looking in the containing directory should find the rename target,
1964
# and anything in a subdir of the renamed target.
1965
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1966
'b/d/e', 'b/g', 'h', 'h/e'],
1970
class TestBisectDirblock(TestCase):
1971
"""Test that bisect_dirblock() returns the expected values.
1973
bisect_dirblock is intended to work like bisect.bisect_left() except it
1974
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1975
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1978
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1979
"""Assert that bisect_split works like bisect_left on the split paths.
1981
:param dirblocks: A list of (path, [info]) pairs.
1982
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1983
:param path: The path we are indexing.
1985
All other arguments will be passed along.
1987
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1989
split_dirblock = (path.split('/'), [])
1990
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1992
self.assertEqual(bisect_left_idx, bisect_split_idx,
1993
'bisect_split disagreed. %s != %s'
1995
% (bisect_left_idx, bisect_split_idx, path)
1998
def paths_to_dirblocks(self, paths):
1999
"""Convert a list of paths into dirblock form.
2001
Also, ensure that the paths are in proper sorted order.
2003
dirblocks = [(path, []) for path in paths]
2004
split_dirblocks = [(path.split('/'), []) for path in paths]
2005
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
2006
return dirblocks, split_dirblocks
2008
def test_simple(self):
2009
"""In the simple case it works just like bisect_left"""
2010
paths = ['', 'a', 'b', 'c', 'd']
2011
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2013
self.assertBisect(dirblocks, split_dirblocks, path)
2014
self.assertBisect(dirblocks, split_dirblocks, '_')
2015
self.assertBisect(dirblocks, split_dirblocks, 'aa')
2016
self.assertBisect(dirblocks, split_dirblocks, 'bb')
2017
self.assertBisect(dirblocks, split_dirblocks, 'cc')
2018
self.assertBisect(dirblocks, split_dirblocks, 'dd')
2019
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
2020
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
2021
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
2022
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
2024
def test_involved(self):
2025
"""This is where bisect_left diverges slightly."""
2027
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
2028
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
2030
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
2031
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
2034
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2036
self.assertBisect(dirblocks, split_dirblocks, path)
2038
def test_involved_cached(self):
2039
"""This is where bisect_left diverges slightly."""
2041
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
2042
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
2044
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
2045
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
2049
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2051
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
2054
class TestDirstateValidation(TestCaseWithDirState):
2056
def test_validate_correct_dirstate(self):
2057
state = self.create_complex_dirstate()
2060
# and make sure we can also validate with a read lock
2067
def test_dirblock_not_sorted(self):
2068
tree, state, expected = self.create_renamed_dirstate()
2069
state._read_dirblocks_if_needed()
2070
last_dirblock = state._dirblocks[-1]
2071
# we're appending to the dirblock, but this name comes before some of
2072
# the existing names; that's wrong
2073
last_dirblock[1].append(
2074
(('h', 'aaaa', 'a-id'),
2075
[('a', '', 0, False, ''),
2076
('a', '', 0, False, '')]))
2077
e = self.assertRaises(AssertionError,
2079
self.assertContainsRe(str(e), 'not sorted')
2081
def test_dirblock_name_mismatch(self):
2082
tree, state, expected = self.create_renamed_dirstate()
2083
state._read_dirblocks_if_needed()
2084
last_dirblock = state._dirblocks[-1]
2085
# add an entry with the wrong directory name
2086
last_dirblock[1].append(
2088
[('a', '', 0, False, ''),
2089
('a', '', 0, False, '')]))
2090
e = self.assertRaises(AssertionError,
2092
self.assertContainsRe(str(e),
2093
"doesn't match directory name")
2095
def test_dirblock_missing_rename(self):
2096
tree, state, expected = self.create_renamed_dirstate()
2097
state._read_dirblocks_if_needed()
2098
last_dirblock = state._dirblocks[-1]
2099
# make another entry for a-id, without a correct 'r' pointer to
2100
# the real occurrence in the working tree
2101
last_dirblock[1].append(
2102
(('h', 'z', 'a-id'),
2103
[('a', '', 0, False, ''),
2104
('a', '', 0, False, '')]))
2105
e = self.assertRaises(AssertionError,
2107
self.assertContainsRe(str(e),
2108
'file a-id is absent in row')