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."""
27
revision as _mod_revision,
29
from bzrlib.memorytree import MemoryTree
30
from bzrlib.tests import (
33
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
self.assertTrue(state._lock_token is not None)
168
self.assertEqual(expected_result[0], state.get_parent_ids())
169
# there should be no ghosts in this tree.
170
self.assertEqual([], state.get_ghosts())
171
# there should be one fileid in this tree - the root of the tree.
172
self.assertEqual(expected_result[1], list(state._iter_entries()))
177
state = dirstate.DirState.on_file('dirstate')
180
self.assertEqual(expected_result[1], list(state._iter_entries()))
184
def create_basic_dirstate(self):
185
"""Create a dirstate with a few files and directories.
195
tree = self.make_branch_and_tree('tree')
196
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
197
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
198
self.build_tree(['tree/' + p for p in paths])
199
tree.set_root_id('TREE_ROOT')
200
tree.add([p.rstrip('/') for p in paths], file_ids)
201
tree.commit('initial', rev_id='rev-1')
202
revision_id = 'rev-1'
203
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
204
t = self.get_transport('tree')
205
a_text = t.get_bytes('a')
206
a_sha = osutils.sha_string(a_text)
208
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
209
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
210
c_text = t.get_bytes('b/c')
211
c_sha = osutils.sha_string(c_text)
213
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
214
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
215
e_text = t.get_bytes('b/d/e')
216
e_sha = osutils.sha_string(e_text)
218
b_c_text = t.get_bytes('b-c')
219
b_c_sha = osutils.sha_string(b_c_text)
220
b_c_len = len(b_c_text)
221
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
222
f_text = t.get_bytes('f')
223
f_sha = osutils.sha_string(f_text)
225
null_stat = dirstate.DirState.NULLSTAT
227
'':(('', '', 'TREE_ROOT'), [
228
('d', '', 0, False, null_stat),
229
('d', '', 0, False, revision_id),
231
'a':(('', 'a', 'a-id'), [
232
('f', '', 0, False, null_stat),
233
('f', a_sha, a_len, False, revision_id),
235
'b':(('', 'b', 'b-id'), [
236
('d', '', 0, False, null_stat),
237
('d', '', 0, False, revision_id),
239
'b/c':(('b', 'c', 'c-id'), [
240
('f', '', 0, False, null_stat),
241
('f', c_sha, c_len, False, revision_id),
243
'b/d':(('b', 'd', 'd-id'), [
244
('d', '', 0, False, null_stat),
245
('d', '', 0, False, revision_id),
247
'b/d/e':(('b/d', 'e', 'e-id'), [
248
('f', '', 0, False, null_stat),
249
('f', e_sha, e_len, False, revision_id),
251
'b-c':(('', 'b-c', 'b-c-id'), [
252
('f', '', 0, False, null_stat),
253
('f', b_c_sha, b_c_len, False, revision_id),
255
'f':(('', 'f', 'f-id'), [
256
('f', '', 0, False, null_stat),
257
('f', f_sha, f_len, False, revision_id),
260
state = dirstate.DirState.from_tree(tree, 'dirstate')
265
# Use a different object, to make sure nothing is pre-cached in memory.
266
state = dirstate.DirState.on_file('dirstate')
268
self.addCleanup(state.unlock)
269
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
270
state._dirblock_state)
271
# This is code is only really tested if we actually have to make more
272
# than one read, so set the page size to something smaller.
273
# We want it to contain about 2.2 records, so that we have a couple
274
# records that we can read per attempt
275
state._bisect_page_size = 200
276
return tree, state, expected
278
def create_duplicated_dirstate(self):
279
"""Create a dirstate with a deleted and added entries.
281
This grabs a basic_dirstate, and then removes and re adds every entry
284
tree, state, expected = self.create_basic_dirstate()
285
# Now we will just remove and add every file so we get an extra entry
286
# per entry. Unversion in reverse order so we handle subdirs
287
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
288
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
289
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
291
# Update the expected dictionary.
292
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
293
orig = expected[path]
295
# This record was deleted in the current tree
296
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
298
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
299
# And didn't exist in the basis tree
300
expected[path2] = (new_key, [orig[1][0],
301
dirstate.DirState.NULL_PARENT_DETAILS])
303
# We will replace the 'dirstate' file underneath 'state', but that is
304
# okay as lock as we unlock 'state' first.
307
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
313
# But we need to leave state in a read-lock because we already have
314
# a cleanup scheduled
316
return tree, state, expected
318
def create_renamed_dirstate(self):
319
"""Create a dirstate with a few internal renames.
321
This takes the basic dirstate, and moves the paths around.
323
tree, state, expected = self.create_basic_dirstate()
325
tree.rename_one('a', 'b/g')
327
tree.rename_one('b/d', 'h')
329
old_a = expected['a']
330
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
331
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
332
('r', 'a', 0, False, '')])
333
old_d = expected['b/d']
334
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
335
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
336
('r', 'b/d', 0, False, '')])
338
old_e = expected['b/d/e']
339
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
341
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
342
('r', 'b/d/e', 0, False, '')])
346
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
353
return tree, state, expected
356
class TestTreeToDirState(TestCaseWithDirState):
358
def test_empty_to_dirstate(self):
359
"""We should be able to create a dirstate for an empty tree."""
360
# There are no files on disk and no parents
361
tree = self.make_branch_and_tree('tree')
362
expected_result = ([], [
363
(('', '', tree.get_root_id()), # common details
364
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
366
state = dirstate.DirState.from_tree(tree, 'dirstate')
368
self.check_state_with_reopen(expected_result, state)
370
def test_1_parents_empty_to_dirstate(self):
371
# create a parent by doing a commit
372
tree = self.make_branch_and_tree('tree')
373
rev_id = tree.commit('first post').encode('utf8')
374
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
375
expected_result = ([rev_id], [
376
(('', '', tree.get_root_id()), # common details
377
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
378
('d', '', 0, False, rev_id), # first parent details
380
state = dirstate.DirState.from_tree(tree, 'dirstate')
381
self.check_state_with_reopen(expected_result, state)
388
def test_2_parents_empty_to_dirstate(self):
389
# create a parent by doing a commit
390
tree = self.make_branch_and_tree('tree')
391
rev_id = tree.commit('first post')
392
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
393
rev_id2 = tree2.commit('second post', allow_pointless=True)
394
tree.merge_from_branch(tree2.branch)
395
expected_result = ([rev_id, rev_id2], [
396
(('', '', tree.get_root_id()), # common details
397
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
398
('d', '', 0, False, rev_id), # first parent details
399
('d', '', 0, False, rev_id2), # second parent details
401
state = dirstate.DirState.from_tree(tree, 'dirstate')
402
self.check_state_with_reopen(expected_result, state)
409
def test_empty_unknowns_are_ignored_to_dirstate(self):
410
"""We should be able to create a dirstate for an empty tree."""
411
# There are no files on disk and no parents
412
tree = self.make_branch_and_tree('tree')
413
self.build_tree(['tree/unknown'])
414
expected_result = ([], [
415
(('', '', tree.get_root_id()), # common details
416
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
418
state = dirstate.DirState.from_tree(tree, 'dirstate')
419
self.check_state_with_reopen(expected_result, state)
421
def get_tree_with_a_file(self):
422
tree = self.make_branch_and_tree('tree')
423
self.build_tree(['tree/a file'])
424
tree.add('a file', 'a-file-id')
427
def test_non_empty_no_parents_to_dirstate(self):
428
"""We should be able to create a dirstate for an empty tree."""
429
# There are files on disk and no parents
430
tree = self.get_tree_with_a_file()
431
expected_result = ([], [
432
(('', '', tree.get_root_id()), # common details
433
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
435
(('', 'a file', 'a-file-id'), # common
436
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
439
state = dirstate.DirState.from_tree(tree, 'dirstate')
440
self.check_state_with_reopen(expected_result, state)
442
def test_1_parents_not_empty_to_dirstate(self):
443
# create a parent by doing a commit
444
tree = self.get_tree_with_a_file()
445
rev_id = tree.commit('first post').encode('utf8')
446
# change the current content to be different this will alter stat, sha
448
self.build_tree_contents([('tree/a file', 'new content\n')])
449
expected_result = ([rev_id], [
450
(('', '', tree.get_root_id()), # common details
451
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
452
('d', '', 0, False, rev_id), # first parent details
454
(('', 'a file', 'a-file-id'), # common
455
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
456
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
457
rev_id), # first parent
460
state = dirstate.DirState.from_tree(tree, 'dirstate')
461
self.check_state_with_reopen(expected_result, state)
463
def test_2_parents_not_empty_to_dirstate(self):
464
# create a parent by doing a commit
465
tree = self.get_tree_with_a_file()
466
rev_id = tree.commit('first post').encode('utf8')
467
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
468
# change the current content to be different this will alter stat, sha
470
self.build_tree_contents([('tree2/a file', 'merge content\n')])
471
rev_id2 = tree2.commit('second post').encode('utf8')
472
tree.merge_from_branch(tree2.branch)
473
# change the current content to be different this will alter stat, sha
474
# and length again, giving us three distinct values:
475
self.build_tree_contents([('tree/a file', 'new content\n')])
476
expected_result = ([rev_id, rev_id2], [
477
(('', '', tree.get_root_id()), # common details
478
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
479
('d', '', 0, False, rev_id), # first parent details
480
('d', '', 0, False, rev_id2), # second parent details
482
(('', 'a file', 'a-file-id'), # common
483
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
484
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
485
rev_id), # first parent
486
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
487
rev_id2), # second parent
490
state = dirstate.DirState.from_tree(tree, 'dirstate')
491
self.check_state_with_reopen(expected_result, state)
493
def test_colliding_fileids(self):
494
# test insertion of parents creating several entries at the same path.
495
# we used to have a bug where they could cause the dirstate to break
496
# its ordering invariants.
497
# create some trees to test from
500
tree = self.make_branch_and_tree('tree%d' % i)
501
self.build_tree(['tree%d/name' % i,])
502
tree.add(['name'], ['file-id%d' % i])
503
revision_id = 'revid-%d' % i
504
tree.commit('message', rev_id=revision_id)
505
parents.append((revision_id,
506
tree.branch.repository.revision_tree(revision_id)))
507
# now fold these trees into a dirstate
508
state = dirstate.DirState.initialize('dirstate')
510
state.set_parent_trees(parents, [])
516
class TestDirStateOnFile(TestCaseWithDirState):
518
def test_construct_with_path(self):
519
tree = self.make_branch_and_tree('tree')
520
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
521
# we want to be able to get the lines of the dirstate that we will
523
lines = state.get_lines()
525
self.build_tree_contents([('dirstate', ''.join(lines))])
527
# no parents, default tree content
528
expected_result = ([], [
529
(('', '', tree.get_root_id()), # common details
530
# current tree details, but new from_tree skips statting, it
531
# uses set_state_from_inventory, and thus depends on the
533
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
536
state = dirstate.DirState.on_file('dirstate')
537
state.lock_write() # check_state_with_reopen will save() and unlock it
538
self.check_state_with_reopen(expected_result, state)
540
def test_can_save_clean_on_file(self):
541
tree = self.make_branch_and_tree('tree')
542
state = dirstate.DirState.from_tree(tree, 'dirstate')
544
# doing a save should work here as there have been no changes.
546
# TODO: stat it and check it hasn't changed; may require waiting
547
# for the state accuracy window.
551
def test_can_save_in_read_lock(self):
552
self.build_tree(['a-file'])
553
state = dirstate.DirState.initialize('dirstate')
555
# No stat and no sha1 sum.
556
state.add('a-file', 'a-file-id', 'file', None, '')
561
# Now open in readonly mode
562
state = dirstate.DirState.on_file('dirstate')
565
entry = state._get_entry(0, path_utf8='a-file')
566
# The current size should be 0 (default)
567
self.assertEqual(0, entry[1][0][2])
568
# We should have a real entry.
569
self.assertNotEqual((None, None), entry)
570
# Make sure everything is old enough
571
state._sha_cutoff_time()
572
state._cutoff_time += 10
573
# Change the file length
574
self.build_tree_contents([('a-file', 'shorter')])
575
sha1sum = dirstate.update_entry(state, entry, 'a-file',
577
# new file, no cached sha:
578
self.assertEqual(None, sha1sum)
580
# The dirblock has been updated
581
self.assertEqual(7, entry[1][0][2])
582
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
583
state._dirblock_state)
586
# Now, since we are the only one holding a lock, we should be able
587
# to save and have it written to disk
592
# Re-open the file, and ensure that the state has been updated.
593
state = dirstate.DirState.on_file('dirstate')
596
entry = state._get_entry(0, path_utf8='a-file')
597
self.assertEqual(7, entry[1][0][2])
601
def test_save_fails_quietly_if_locked(self):
602
"""If dirstate is locked, save will fail without complaining."""
603
self.build_tree(['a-file'])
604
state = dirstate.DirState.initialize('dirstate')
606
# No stat and no sha1 sum.
607
state.add('a-file', 'a-file-id', 'file', None, '')
612
state = dirstate.DirState.on_file('dirstate')
615
entry = state._get_entry(0, path_utf8='a-file')
616
sha1sum = dirstate.update_entry(state, entry, 'a-file',
619
self.assertEqual(None, sha1sum)
620
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
621
state._dirblock_state)
623
# Now, before we try to save, grab another dirstate, and take out a
625
# TODO: jam 20070315 Ideally this would be locked by another
626
# process. To make sure the file is really OS locked.
627
state2 = dirstate.DirState.on_file('dirstate')
630
# This won't actually write anything, because it couldn't grab
631
# a write lock. But it shouldn't raise an error, either.
632
# TODO: jam 20070315 We should probably distinguish between
633
# being dirty because of 'update_entry'. And dirty
634
# because of real modification. So that save() *does*
635
# raise a real error if it fails when we have real
643
# The file on disk should not be modified.
644
state = dirstate.DirState.on_file('dirstate')
647
entry = state._get_entry(0, path_utf8='a-file')
648
self.assertEqual('', entry[1][0][1])
652
def test_save_refuses_if_changes_aborted(self):
653
self.build_tree(['a-file', 'a-dir/'])
654
state = dirstate.DirState.initialize('dirstate')
656
# No stat and no sha1 sum.
657
state.add('a-file', 'a-file-id', 'file', None, '')
662
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
664
('', [(('', '', 'TREE_ROOT'),
665
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
666
('', [(('', 'a-file', 'a-file-id'),
667
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
670
state = dirstate.DirState.on_file('dirstate')
673
state._read_dirblocks_if_needed()
674
self.assertEqual(expected_blocks, state._dirblocks)
676
# Now modify the state, but mark it as inconsistent
677
state.add('a-dir', 'a-dir-id', 'directory', None, '')
678
state._changes_aborted = True
683
state = dirstate.DirState.on_file('dirstate')
686
state._read_dirblocks_if_needed()
687
self.assertEqual(expected_blocks, state._dirblocks)
692
class TestDirStateInitialize(TestCaseWithDirState):
694
def test_initialize(self):
695
expected_result = ([], [
696
(('', '', 'TREE_ROOT'), # common details
697
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
700
state = dirstate.DirState.initialize('dirstate')
702
self.assertIsInstance(state, dirstate.DirState)
703
lines = state.get_lines()
706
# On win32 you can't read from a locked file, even within the same
707
# process. So we have to unlock and release before we check the file
709
self.assertFileEqual(''.join(lines), 'dirstate')
710
state.lock_read() # check_state_with_reopen will unlock
711
self.check_state_with_reopen(expected_result, state)
714
class TestDirStateManipulations(TestCaseWithDirState):
716
def test_set_state_from_inventory_no_content_no_parents(self):
717
# setting the current inventory is a slow but important api to support.
718
tree1 = self.make_branch_and_memory_tree('tree1')
722
revid1 = tree1.commit('foo').encode('utf8')
723
root_id = tree1.get_root_id()
724
inv = tree1.inventory
727
expected_result = [], [
728
(('', '', root_id), [
729
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
730
state = dirstate.DirState.initialize('dirstate')
732
state.set_state_from_inventory(inv)
733
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
735
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
736
state._dirblock_state)
741
# This will unlock it
742
self.check_state_with_reopen(expected_result, state)
744
def test_set_state_from_inventory_preserves_hashcache(self):
745
# https://bugs.launchpad.net/bzr/+bug/146176
746
# set_state_from_inventory should preserve the stat and hash value for
747
# workingtree files that are not changed by the inventory.
749
tree = self.make_branch_and_tree('.')
750
# depends on the default format using dirstate...
753
# make a dirstate with some valid hashcache data
754
# file on disk, but that's not needed for this test
755
foo_contents = 'contents of foo'
756
self.build_tree_contents([('foo', foo_contents)])
757
tree.add('foo', 'foo-id')
759
foo_stat = os.stat('foo')
760
foo_packed = dirstate.pack_stat(foo_stat)
761
foo_sha = osutils.sha_string(foo_contents)
762
foo_size = len(foo_contents)
764
# should not be cached yet, because the file's too fresh
766
(('', 'foo', 'foo-id',),
767
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
768
tree._dirstate._get_entry(0, 'foo-id'))
769
# poke in some hashcache information - it wouldn't normally be
770
# stored because it's too fresh
771
tree._dirstate.update_minimal(
772
('', 'foo', 'foo-id'),
773
'f', False, foo_sha, foo_packed, foo_size, 'foo')
774
# now should be cached
776
(('', 'foo', 'foo-id',),
777
[('f', foo_sha, foo_size, False, foo_packed)]),
778
tree._dirstate._get_entry(0, 'foo-id'))
780
# extract the inventory, and add something to it
781
inv = tree._get_inventory()
782
# should see the file we poked in...
783
self.assertTrue(inv.has_id('foo-id'))
784
self.assertTrue(inv.has_filename('foo'))
785
inv.add_path('bar', 'file', 'bar-id')
786
tree._dirstate._validate()
787
# this used to cause it to lose its hashcache
788
tree._dirstate.set_state_from_inventory(inv)
789
tree._dirstate._validate()
795
# now check that the state still has the original hashcache value
796
state = tree._dirstate
798
foo_tuple = state._get_entry(0, path_utf8='foo')
800
(('', 'foo', 'foo-id',),
801
[('f', foo_sha, len(foo_contents), False,
802
dirstate.pack_stat(foo_stat))]),
808
def test_set_state_from_inventory_mixed_paths(self):
809
tree1 = self.make_branch_and_tree('tree1')
810
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
811
'tree1/a/b/foo', 'tree1/a-b/bar'])
814
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
815
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
816
tree1.commit('rev1', rev_id='rev1')
817
root_id = tree1.get_root_id()
818
inv = tree1.inventory
821
expected_result1 = [('', '', root_id, 'd'),
822
('', 'a', 'a-id', 'd'),
823
('', 'a-b', 'a-b-id', 'd'),
824
('a', 'b', 'b-id', 'd'),
825
('a/b', 'foo', 'foo-id', 'f'),
826
('a-b', 'bar', 'bar-id', 'f'),
828
expected_result2 = [('', '', root_id, 'd'),
829
('', 'a', 'a-id', 'd'),
830
('', 'a-b', 'a-b-id', 'd'),
831
('a-b', 'bar', 'bar-id', 'f'),
833
state = dirstate.DirState.initialize('dirstate')
835
state.set_state_from_inventory(inv)
837
for entry in state._iter_entries():
838
values.append(entry[0] + entry[1][0][:1])
839
self.assertEqual(expected_result1, values)
841
state.set_state_from_inventory(inv)
843
for entry in state._iter_entries():
844
values.append(entry[0] + entry[1][0][:1])
845
self.assertEqual(expected_result2, values)
849
def test_set_path_id_no_parents(self):
850
"""The id of a path can be changed trivally with no parents."""
851
state = dirstate.DirState.initialize('dirstate')
853
# check precondition to be sure the state does change appropriately.
855
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
856
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
857
list(state._iter_entries()))
858
state.set_path_id('', 'foobarbaz')
860
(('', '', 'foobarbaz'), [('d', '', 0, False,
861
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
862
self.assertEqual(expected_rows, list(state._iter_entries()))
863
# should work across save too
867
state = dirstate.DirState.on_file('dirstate')
871
self.assertEqual(expected_rows, list(state._iter_entries()))
875
def test_set_path_id_with_parents(self):
876
"""Set the root file id in a dirstate with parents"""
877
mt = self.make_branch_and_tree('mt')
878
# in case the default tree format uses a different root id
879
mt.set_root_id('TREE_ROOT')
880
mt.commit('foo', rev_id='parent-revid')
881
rt = mt.branch.repository.revision_tree('parent-revid')
882
state = dirstate.DirState.initialize('dirstate')
885
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
886
state.set_path_id('', 'foobarbaz')
888
# now see that it is what we expected
890
(('', '', 'TREE_ROOT'),
891
[('a', '', 0, False, ''),
892
('d', '', 0, False, 'parent-revid'),
894
(('', '', 'foobarbaz'),
895
[('d', '', 0, False, ''),
896
('a', '', 0, False, ''),
900
self.assertEqual(expected_rows, list(state._iter_entries()))
901
# should work across save too
905
# now flush & check we get the same
906
state = dirstate.DirState.on_file('dirstate')
910
self.assertEqual(expected_rows, list(state._iter_entries()))
913
# now change within an existing file-backed state
917
state.set_path_id('', 'tree-root-2')
923
def test_set_parent_trees_no_content(self):
924
# set_parent_trees is a slow but important api to support.
925
tree1 = self.make_branch_and_memory_tree('tree1')
929
revid1 = tree1.commit('foo')
932
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
933
tree2 = MemoryTree.create_on_branch(branch2)
936
revid2 = tree2.commit('foo')
937
root_id = tree2.get_root_id()
940
state = dirstate.DirState.initialize('dirstate')
942
state.set_path_id('', root_id)
943
state.set_parent_trees(
944
((revid1, tree1.branch.repository.revision_tree(revid1)),
945
(revid2, tree2.branch.repository.revision_tree(revid2)),
946
('ghost-rev', None)),
948
# check we can reopen and use the dirstate after setting parent
955
state = dirstate.DirState.on_file('dirstate')
958
self.assertEqual([revid1, revid2, 'ghost-rev'],
959
state.get_parent_ids())
960
# iterating the entire state ensures that the state is parsable.
961
list(state._iter_entries())
962
# be sure that it sets not appends - change it
963
state.set_parent_trees(
964
((revid1, tree1.branch.repository.revision_tree(revid1)),
965
('ghost-rev', None)),
967
# and now put it back.
968
state.set_parent_trees(
969
((revid1, tree1.branch.repository.revision_tree(revid1)),
970
(revid2, tree2.branch.repository.revision_tree(revid2)),
971
('ghost-rev', tree2.branch.repository.revision_tree(
972
_mod_revision.NULL_REVISION))),
974
self.assertEqual([revid1, revid2, 'ghost-rev'],
975
state.get_parent_ids())
976
# the ghost should be recorded as such by set_parent_trees.
977
self.assertEqual(['ghost-rev'], state.get_ghosts())
979
[(('', '', root_id), [
980
('d', '', 0, False, dirstate.DirState.NULLSTAT),
981
('d', '', 0, False, revid1),
982
('d', '', 0, False, revid2)
984
list(state._iter_entries()))
988
def test_set_parent_trees_file_missing_from_tree(self):
989
# Adding a parent tree may reference files not in the current state.
990
# they should get listed just once by id, even if they are in two
992
# set_parent_trees is a slow but important api to support.
993
tree1 = self.make_branch_and_memory_tree('tree1')
997
tree1.add(['a file'], ['file-id'], ['file'])
998
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
999
revid1 = tree1.commit('foo')
1002
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1003
tree2 = MemoryTree.create_on_branch(branch2)
1006
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1007
revid2 = tree2.commit('foo')
1008
root_id = tree2.get_root_id()
1011
# check the layout in memory
1012
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1013
(('', '', root_id), [
1014
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1015
('d', '', 0, False, revid1.encode('utf8')),
1016
('d', '', 0, False, revid2.encode('utf8'))
1018
(('', 'a file', 'file-id'), [
1019
('a', '', 0, False, ''),
1020
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1021
revid1.encode('utf8')),
1022
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1023
revid2.encode('utf8'))
1026
state = dirstate.DirState.initialize('dirstate')
1028
state.set_path_id('', root_id)
1029
state.set_parent_trees(
1030
((revid1, tree1.branch.repository.revision_tree(revid1)),
1031
(revid2, tree2.branch.repository.revision_tree(revid2)),
1037
# check_state_with_reopen will unlock
1038
self.check_state_with_reopen(expected_result, state)
1040
### add a path via _set_data - so we dont need delta work, just
1041
# raw data in, and ensure that it comes out via get_lines happily.
1043
def test_add_path_to_root_no_parents_all_data(self):
1044
# The most trivial addition of a path is when there are no parents and
1045
# its in the root and all data about the file is supplied
1046
self.build_tree(['a file'])
1047
stat = os.lstat('a file')
1048
# the 1*20 is the sha1 pretend value.
1049
state = dirstate.DirState.initialize('dirstate')
1050
expected_entries = [
1051
(('', '', 'TREE_ROOT'), [
1052
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1054
(('', 'a file', 'a-file-id'), [
1055
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1059
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1060
# having added it, it should be in the output of iter_entries.
1061
self.assertEqual(expected_entries, list(state._iter_entries()))
1062
# saving and reloading should not affect this.
1066
state = dirstate.DirState.on_file('dirstate')
1069
self.assertEqual(expected_entries, list(state._iter_entries()))
1073
def test_add_path_to_unversioned_directory(self):
1074
"""Adding a path to an unversioned directory should error.
1076
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1077
once dirstate is stable and if it is merged with WorkingTree3, consider
1078
removing this copy of the test.
1080
self.build_tree(['unversioned/', 'unversioned/a file'])
1081
state = dirstate.DirState.initialize('dirstate')
1083
self.assertRaises(errors.NotVersionedError, state.add,
1084
'unversioned/a file', 'a-file-id', 'file', None, None)
1088
def test_add_directory_to_root_no_parents_all_data(self):
1089
# The most trivial addition of a dir is when there are no parents and
1090
# its in the root and all data about the file is supplied
1091
self.build_tree(['a dir/'])
1092
stat = os.lstat('a dir')
1093
expected_entries = [
1094
(('', '', 'TREE_ROOT'), [
1095
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1097
(('', 'a dir', 'a dir id'), [
1098
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
1101
state = dirstate.DirState.initialize('dirstate')
1103
state.add('a dir', 'a dir id', 'directory', stat, None)
1104
# having added it, it should be in the output of iter_entries.
1105
self.assertEqual(expected_entries, list(state._iter_entries()))
1106
# saving and reloading should not affect this.
1110
state = dirstate.DirState.on_file('dirstate')
1114
self.assertEqual(expected_entries, list(state._iter_entries()))
1118
def test_add_symlink_to_root_no_parents_all_data(self):
1119
# The most trivial addition of a symlink when there are no parents and
1120
# its in the root and all data about the file is supplied
1121
# bzr doesn't support fake symlinks on windows, yet.
1122
self.requireFeature(SymlinkFeature)
1123
os.symlink('target', 'a link')
1124
stat = os.lstat('a link')
1125
expected_entries = [
1126
(('', '', 'TREE_ROOT'), [
1127
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1129
(('', 'a link', 'a link id'), [
1130
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1133
state = dirstate.DirState.initialize('dirstate')
1135
state.add('a link', 'a link id', 'symlink', stat, 'target')
1136
# having added it, it should be in the output of iter_entries.
1137
self.assertEqual(expected_entries, list(state._iter_entries()))
1138
# saving and reloading should not affect this.
1142
state = dirstate.DirState.on_file('dirstate')
1145
self.assertEqual(expected_entries, list(state._iter_entries()))
1149
def test_add_directory_and_child_no_parents_all_data(self):
1150
# after adding a directory, we should be able to add children to it.
1151
self.build_tree(['a dir/', 'a dir/a file'])
1152
dirstat = os.lstat('a dir')
1153
filestat = os.lstat('a dir/a file')
1154
expected_entries = [
1155
(('', '', 'TREE_ROOT'), [
1156
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1158
(('', 'a dir', 'a dir id'), [
1159
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1161
(('a dir', 'a file', 'a-file-id'), [
1162
('f', '1'*20, 25, False,
1163
dirstate.pack_stat(filestat)), # current tree details
1166
state = dirstate.DirState.initialize('dirstate')
1168
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1169
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1170
# added it, it should be in the output of iter_entries.
1171
self.assertEqual(expected_entries, list(state._iter_entries()))
1172
# saving and reloading should not affect this.
1176
state = dirstate.DirState.on_file('dirstate')
1179
self.assertEqual(expected_entries, list(state._iter_entries()))
1183
def test_add_tree_reference(self):
1184
# make a dirstate and add a tree reference
1185
state = dirstate.DirState.initialize('dirstate')
1187
('', 'subdir', 'subdir-id'),
1188
[('t', 'subtree-123123', 0, False,
1189
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1192
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1193
entry = state._get_entry(0, 'subdir-id', 'subdir')
1194
self.assertEqual(entry, expected_entry)
1199
# now check we can read it back
1203
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1204
self.assertEqual(entry, entry2)
1205
self.assertEqual(entry, expected_entry)
1206
# and lookup by id should work too
1207
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1208
self.assertEqual(entry, expected_entry)
1212
def test_add_forbidden_names(self):
1213
state = dirstate.DirState.initialize('dirstate')
1214
self.addCleanup(state.unlock)
1215
self.assertRaises(errors.BzrError,
1216
state.add, '.', 'ass-id', 'directory', None, None)
1217
self.assertRaises(errors.BzrError,
1218
state.add, '..', 'ass-id', 'directory', None, None)
1221
class TestGetLines(TestCaseWithDirState):
1223
def test_get_line_with_2_rows(self):
1224
state = self.create_dirstate_with_root_and_subdir()
1226
self.assertEqual(['#bazaar dirstate flat format 3\n',
1227
'crc32: 41262208\n',
1231
'\x00\x00a-root-value\x00'
1232
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1233
'\x00subdir\x00subdir-id\x00'
1234
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1235
], state.get_lines())
1239
def test_entry_to_line(self):
1240
state = self.create_dirstate_with_root()
1243
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1244
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1245
state._entry_to_line(state._dirblocks[0][1][0]))
1249
def test_entry_to_line_with_parent(self):
1250
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1251
root_entry = ('', '', 'a-root-value'), [
1252
('d', '', 0, False, packed_stat), # current tree details
1253
# first: a pointer to the current location
1254
('a', 'dirname/basename', 0, False, ''),
1256
state = dirstate.DirState.initialize('dirstate')
1259
'\x00\x00a-root-value\x00'
1260
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1261
'a\x00dirname/basename\x000\x00n\x00',
1262
state._entry_to_line(root_entry))
1266
def test_entry_to_line_with_two_parents_at_different_paths(self):
1267
# / in the tree, at / in one parent and /dirname/basename in the other.
1268
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1269
root_entry = ('', '', 'a-root-value'), [
1270
('d', '', 0, False, packed_stat), # current tree details
1271
('d', '', 0, False, 'rev_id'), # first parent details
1272
# second: a pointer to the current location
1273
('a', 'dirname/basename', 0, False, ''),
1275
state = dirstate.DirState.initialize('dirstate')
1278
'\x00\x00a-root-value\x00'
1279
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1280
'd\x00\x000\x00n\x00rev_id\x00'
1281
'a\x00dirname/basename\x000\x00n\x00',
1282
state._entry_to_line(root_entry))
1286
def test_iter_entries(self):
1287
# we should be able to iterate the dirstate entries from end to end
1288
# this is for get_lines to be easy to read.
1289
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1291
root_entries = [(('', '', 'a-root-value'), [
1292
('d', '', 0, False, packed_stat), # current tree details
1294
dirblocks.append(('', root_entries))
1295
# add two files in the root
1296
subdir_entry = ('', 'subdir', 'subdir-id'), [
1297
('d', '', 0, False, packed_stat), # current tree details
1299
afile_entry = ('', 'afile', 'afile-id'), [
1300
('f', 'sha1value', 34, False, packed_stat), # current tree details
1302
dirblocks.append(('', [subdir_entry, afile_entry]))
1304
file_entry2 = ('subdir', '2file', '2file-id'), [
1305
('f', 'sha1value', 23, False, packed_stat), # current tree details
1307
dirblocks.append(('subdir', [file_entry2]))
1308
state = dirstate.DirState.initialize('dirstate')
1310
state._set_data([], dirblocks)
1311
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1313
self.assertEqual(expected_entries, list(state._iter_entries()))
1318
class TestGetBlockRowIndex(TestCaseWithDirState):
1320
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1321
file_present, state, dirname, basename, tree_index):
1322
self.assertEqual((block_index, row_index, dir_present, file_present),
1323
state._get_block_entry_index(dirname, basename, tree_index))
1325
block = state._dirblocks[block_index]
1326
self.assertEqual(dirname, block[0])
1327
if dir_present and file_present:
1328
row = state._dirblocks[block_index][1][row_index]
1329
self.assertEqual(dirname, row[0][0])
1330
self.assertEqual(basename, row[0][1])
1332
def test_simple_structure(self):
1333
state = self.create_dirstate_with_root_and_subdir()
1334
self.addCleanup(state.unlock)
1335
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1336
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1337
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1338
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1339
self.assertBlockRowIndexEqual(2, 0, False, False, state,
1342
def test_complex_structure_exists(self):
1343
state = self.create_complex_dirstate()
1344
self.addCleanup(state.unlock)
1345
# Make sure we can find everything that exists
1346
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1347
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1348
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1349
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1350
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1351
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1352
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1353
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1354
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1355
'b', 'h\xc3\xa5', 0)
1357
def test_complex_structure_missing(self):
1358
state = self.create_complex_dirstate()
1359
self.addCleanup(state.unlock)
1360
# Make sure things would be inserted in the right locations
1361
# '_' comes before 'a'
1362
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1363
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1364
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1365
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1367
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1368
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1369
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
1370
# This would be inserted between a/ and b/
1371
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
1373
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1376
class TestGetEntry(TestCaseWithDirState):
1378
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1379
"""Check that the right entry is returned for a request to getEntry."""
1380
entry = state._get_entry(index, path_utf8=path)
1382
self.assertEqual((None, None), entry)
1385
self.assertEqual((dirname, basename, file_id), cur[:3])
1387
def test_simple_structure(self):
1388
state = self.create_dirstate_with_root_and_subdir()
1389
self.addCleanup(state.unlock)
1390
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1391
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1392
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1393
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1394
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
1396
def test_complex_structure_exists(self):
1397
state = self.create_complex_dirstate()
1398
self.addCleanup(state.unlock)
1399
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1400
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1401
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1402
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1403
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1404
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1405
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1406
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1407
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
1410
def test_complex_structure_missing(self):
1411
state = self.create_complex_dirstate()
1412
self.addCleanup(state.unlock)
1413
self.assertEntryEqual(None, None, None, state, '_', 0)
1414
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1415
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1416
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
1418
def test_get_entry_uninitialized(self):
1419
"""Calling get_entry will load data if it needs to"""
1420
state = self.create_dirstate_with_root()
1426
state = dirstate.DirState.on_file('dirstate')
1429
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1430
state._header_state)
1431
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1432
state._dirblock_state)
1433
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1438
class TestIterChildEntries(TestCaseWithDirState):
1440
def create_dirstate_with_two_trees(self):
1441
"""This dirstate contains multiple files and directories.
1451
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1453
Notice that a/e is an empty directory.
1455
There is one parent tree, which has the same shape with the following variations:
1456
b/g in the parent is gone.
1457
b/h in the parent has a different id
1458
b/i is new in the parent
1459
c is renamed to b/j in the parent
1461
:return: The dirstate, still write-locked.
1463
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1464
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1465
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1466
root_entry = ('', '', 'a-root-value'), [
1467
('d', '', 0, False, packed_stat),
1468
('d', '', 0, False, 'parent-revid'),
1470
a_entry = ('', 'a', 'a-dir'), [
1471
('d', '', 0, False, packed_stat),
1472
('d', '', 0, False, 'parent-revid'),
1474
b_entry = ('', 'b', 'b-dir'), [
1475
('d', '', 0, False, packed_stat),
1476
('d', '', 0, False, 'parent-revid'),
1478
c_entry = ('', 'c', 'c-file'), [
1479
('f', null_sha, 10, False, packed_stat),
1480
('r', 'b/j', 0, False, ''),
1482
d_entry = ('', 'd', 'd-file'), [
1483
('f', null_sha, 20, False, packed_stat),
1484
('f', 'd', 20, False, 'parent-revid'),
1486
e_entry = ('a', 'e', 'e-dir'), [
1487
('d', '', 0, False, packed_stat),
1488
('d', '', 0, False, 'parent-revid'),
1490
f_entry = ('a', 'f', 'f-file'), [
1491
('f', null_sha, 30, False, packed_stat),
1492
('f', 'f', 20, False, 'parent-revid'),
1494
g_entry = ('b', 'g', 'g-file'), [
1495
('f', null_sha, 30, False, packed_stat),
1496
NULL_PARENT_DETAILS,
1498
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1499
('f', null_sha, 40, False, packed_stat),
1500
NULL_PARENT_DETAILS,
1502
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1503
NULL_PARENT_DETAILS,
1504
('f', 'h', 20, False, 'parent-revid'),
1506
i_entry = ('b', 'i', 'i-file'), [
1507
NULL_PARENT_DETAILS,
1508
('f', 'h', 20, False, 'parent-revid'),
1510
j_entry = ('b', 'j', 'c-file'), [
1511
('r', 'c', 0, False, ''),
1512
('f', 'j', 20, False, 'parent-revid'),
1515
dirblocks.append(('', [root_entry]))
1516
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1517
dirblocks.append(('a', [e_entry, f_entry]))
1518
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1519
state = dirstate.DirState.initialize('dirstate')
1522
state._set_data(['parent'], dirblocks)
1526
return state, dirblocks
1528
def test_iter_children_b(self):
1529
state, dirblocks = self.create_dirstate_with_two_trees()
1530
self.addCleanup(state.unlock)
1531
expected_result = []
1532
expected_result.append(dirblocks[3][1][2]) # h2
1533
expected_result.append(dirblocks[3][1][3]) # i
1534
expected_result.append(dirblocks[3][1][4]) # j
1535
self.assertEqual(expected_result,
1536
list(state._iter_child_entries(1, 'b')))
1538
def test_iter_child_root(self):
1539
state, dirblocks = self.create_dirstate_with_two_trees()
1540
self.addCleanup(state.unlock)
1541
expected_result = []
1542
expected_result.append(dirblocks[1][1][0]) # a
1543
expected_result.append(dirblocks[1][1][1]) # b
1544
expected_result.append(dirblocks[1][1][3]) # d
1545
expected_result.append(dirblocks[2][1][0]) # e
1546
expected_result.append(dirblocks[2][1][1]) # f
1547
expected_result.append(dirblocks[3][1][2]) # h2
1548
expected_result.append(dirblocks[3][1][3]) # i
1549
expected_result.append(dirblocks[3][1][4]) # j
1550
self.assertEqual(expected_result,
1551
list(state._iter_child_entries(1, '')))
1554
class TestDirstateSortOrder(TestCaseWithTransport):
1555
"""Test that DirState adds entries in the right order."""
1557
def test_add_sorting(self):
1558
"""Add entries in lexicographical order, we get path sorted order.
1560
This tests it to a depth of 4, to make sure we don't just get it right
1561
at a single depth. 'a/a' should come before 'a-a', even though it
1562
doesn't lexicographically.
1564
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1565
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1568
state = dirstate.DirState.initialize('dirstate')
1569
self.addCleanup(state.unlock)
1571
fake_stat = os.stat('dirstate')
1573
d_id = d.replace('/', '_')+'-id'
1574
file_path = d + '/f'
1575
file_id = file_path.replace('/', '_')+'-id'
1576
state.add(d, d_id, 'directory', fake_stat, null_sha)
1577
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1579
expected = ['', '', 'a',
1580
'a/a', 'a/a/a', 'a/a/a/a',
1581
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1583
split = lambda p:p.split('/')
1584
self.assertEqual(sorted(expected, key=split), expected)
1585
dirblock_names = [d[0] for d in state._dirblocks]
1586
self.assertEqual(expected, dirblock_names)
1588
def test_set_parent_trees_correct_order(self):
1589
"""After calling set_parent_trees() we should maintain the order."""
1590
dirs = ['a', 'a-a', 'a/a']
1592
state = dirstate.DirState.initialize('dirstate')
1593
self.addCleanup(state.unlock)
1595
fake_stat = os.stat('dirstate')
1597
d_id = d.replace('/', '_')+'-id'
1598
file_path = d + '/f'
1599
file_id = file_path.replace('/', '_')+'-id'
1600
state.add(d, d_id, 'directory', fake_stat, null_sha)
1601
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1603
expected = ['', '', 'a', 'a/a', 'a-a']
1604
dirblock_names = [d[0] for d in state._dirblocks]
1605
self.assertEqual(expected, dirblock_names)
1607
# *really* cheesy way to just get an empty tree
1608
repo = self.make_repository('repo')
1609
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1610
state.set_parent_trees([('null:', empty_tree)], [])
1612
dirblock_names = [d[0] for d in state._dirblocks]
1613
self.assertEqual(expected, dirblock_names)
1616
class InstrumentedDirState(dirstate.DirState):
1617
"""An DirState with instrumented sha1 functionality."""
1619
def __init__(self, path):
1620
super(InstrumentedDirState, self).__init__(path)
1621
self._time_offset = 0
1623
# member is dynamically set in DirState.__init__ to turn on trace
1624
self._sha1_file = self._sha1_file_and_log
1626
def _sha_cutoff_time(self):
1627
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1628
self._cutoff_time = timestamp + self._time_offset
1630
def _sha1_file_and_log(self, abspath):
1631
self._log.append(('sha1', abspath))
1632
return osutils.sha_file_by_name(abspath)
1634
def _read_link(self, abspath, old_link):
1635
self._log.append(('read_link', abspath, old_link))
1636
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1638
def _lstat(self, abspath, entry):
1639
self._log.append(('lstat', abspath))
1640
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1642
def _is_executable(self, mode, old_executable):
1643
self._log.append(('is_exec', mode, old_executable))
1644
return super(InstrumentedDirState, self)._is_executable(mode,
1647
def adjust_time(self, secs):
1648
"""Move the clock forward or back.
1650
:param secs: The amount to adjust the clock by. Positive values make it
1651
seem as if we are in the future, negative values make it seem like we
1654
self._time_offset += secs
1655
self._cutoff_time = None
1658
class _FakeStat(object):
1659
"""A class with the same attributes as a real stat result."""
1661
def __init__(self, size, mtime, ctime, dev, ino, mode):
1663
self.st_mtime = mtime
1664
self.st_ctime = ctime
1670
class TestPackStat(TestCaseWithTransport):
1672
def assertPackStat(self, expected, stat_value):
1673
"""Check the packed and serialized form of a stat value."""
1674
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1676
def test_pack_stat_int(self):
1677
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1678
# Make sure that all parameters have an impact on the packed stat.
1679
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1682
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1683
st.st_mtime = 1172758620
1685
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1686
st.st_ctime = 1172758630
1688
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1691
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1692
st.st_ino = 6499540L
1694
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1695
st.st_mode = 0100744
1697
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1699
def test_pack_stat_float(self):
1700
"""On some platforms mtime and ctime are floats.
1702
Make sure we don't get warnings or errors, and that we ignore changes <
1705
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1706
777L, 6499538L, 0100644)
1707
# These should all be the same as the integer counterparts
1708
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1709
st.st_mtime = 1172758620.0
1711
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1712
st.st_ctime = 1172758630.0
1714
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1715
# fractional seconds are discarded, so no change from above
1716
st.st_mtime = 1172758620.453
1717
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1718
st.st_ctime = 1172758630.228
1719
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1722
class TestBisect(TestCaseWithDirState):
1723
"""Test the ability to bisect into the disk format."""
1725
def assertBisect(self, expected_map, map_keys, state, paths):
1726
"""Assert that bisecting for paths returns the right result.
1728
:param expected_map: A map from key => entry value
1729
:param map_keys: The keys to expect for each path
1730
:param state: The DirState object.
1731
:param paths: A list of paths, these will automatically be split into
1732
(dir, name) tuples, and sorted according to how _bisect
1735
result = state._bisect(paths)
1736
# For now, results are just returned in whatever order we read them.
1737
# We could sort by (dir, name, file_id) or something like that, but in
1738
# the end it would still be fairly arbitrary, and we don't want the
1739
# extra overhead if we can avoid it. So sort everything to make sure
1741
self.assertEqual(len(map_keys), len(paths))
1743
for path, keys in zip(paths, map_keys):
1745
# This should not be present in the output
1747
expected[path] = sorted(expected_map[k] for k in keys)
1749
# The returned values are just arranged randomly based on when they
1750
# were read, for testing, make sure it is properly sorted.
1754
self.assertEqual(expected, result)
1756
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1757
"""Assert that bisecting for dirbblocks returns the right result.
1759
:param expected_map: A map from key => expected values
1760
:param map_keys: A nested list of paths we expect to be returned.
1761
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1762
:param state: The DirState object.
1763
:param paths: A list of directories
1765
result = state._bisect_dirblocks(paths)
1766
self.assertEqual(len(map_keys), len(paths))
1768
for path, keys in zip(paths, map_keys):
1770
# This should not be present in the output
1772
expected[path] = sorted(expected_map[k] for k in keys)
1776
self.assertEqual(expected, result)
1778
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1779
"""Assert the return value of a recursive bisection.
1781
:param expected_map: A map from key => entry value
1782
:param map_keys: A list of paths we expect to be returned.
1783
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1784
:param state: The DirState object.
1785
:param paths: A list of files and directories. It will be broken up
1786
into (dir, name) pairs and sorted before calling _bisect_recursive.
1789
for key in map_keys:
1790
entry = expected_map[key]
1791
dir_name_id, trees_info = entry
1792
expected[dir_name_id] = trees_info
1794
result = state._bisect_recursive(paths)
1796
self.assertEqual(expected, result)
1798
def test_bisect_each(self):
1799
"""Find a single record using bisect."""
1800
tree, state, expected = self.create_basic_dirstate()
1802
# Bisect should return the rows for the specified files.
1803
self.assertBisect(expected, [['']], state, [''])
1804
self.assertBisect(expected, [['a']], state, ['a'])
1805
self.assertBisect(expected, [['b']], state, ['b'])
1806
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1807
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1808
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1809
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1810
self.assertBisect(expected, [['f']], state, ['f'])
1812
def test_bisect_multi(self):
1813
"""Bisect can be used to find multiple records at the same time."""
1814
tree, state, expected = self.create_basic_dirstate()
1815
# Bisect should be capable of finding multiple entries at the same time
1816
self.assertBisect(expected, [['a'], ['b'], ['f']],
1817
state, ['a', 'b', 'f'])
1818
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1819
state, ['f', 'b/d', 'b/d/e'])
1820
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1821
state, ['b', 'b-c', 'b/c'])
1823
def test_bisect_one_page(self):
1824
"""Test bisect when there is only 1 page to read"""
1825
tree, state, expected = self.create_basic_dirstate()
1826
state._bisect_page_size = 5000
1827
self.assertBisect(expected,[['']], state, [''])
1828
self.assertBisect(expected,[['a']], state, ['a'])
1829
self.assertBisect(expected,[['b']], state, ['b'])
1830
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1831
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1832
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1833
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1834
self.assertBisect(expected,[['f']], state, ['f'])
1835
self.assertBisect(expected,[['a'], ['b'], ['f']],
1836
state, ['a', 'b', 'f'])
1837
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1838
state, ['b/d', 'b/d/e', 'f'])
1839
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1840
state, ['b', 'b/c', 'b-c'])
1842
def test_bisect_duplicate_paths(self):
1843
"""When bisecting for a path, handle multiple entries."""
1844
tree, state, expected = self.create_duplicated_dirstate()
1846
# Now make sure that both records are properly returned.
1847
self.assertBisect(expected, [['']], state, [''])
1848
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1849
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1850
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1851
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1852
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1854
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1855
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1857
def test_bisect_page_size_too_small(self):
1858
"""If the page size is too small, we will auto increase it."""
1859
tree, state, expected = self.create_basic_dirstate()
1860
state._bisect_page_size = 50
1861
self.assertBisect(expected, [None], state, ['b/e'])
1862
self.assertBisect(expected, [['a']], state, ['a'])
1863
self.assertBisect(expected, [['b']], state, ['b'])
1864
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1865
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1866
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1867
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1868
self.assertBisect(expected, [['f']], state, ['f'])
1870
def test_bisect_missing(self):
1871
"""Test that bisect return None if it cannot find a path."""
1872
tree, state, expected = self.create_basic_dirstate()
1873
self.assertBisect(expected, [None], state, ['foo'])
1874
self.assertBisect(expected, [None], state, ['b/foo'])
1875
self.assertBisect(expected, [None], state, ['bar/foo'])
1876
self.assertBisect(expected, [None], state, ['b-c/foo'])
1878
self.assertBisect(expected, [['a'], None, ['b/d']],
1879
state, ['a', 'foo', 'b/d'])
1881
def test_bisect_rename(self):
1882
"""Check that we find a renamed row."""
1883
tree, state, expected = self.create_renamed_dirstate()
1885
# Search for the pre and post renamed entries
1886
self.assertBisect(expected, [['a']], state, ['a'])
1887
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1888
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1889
self.assertBisect(expected, [['h']], state, ['h'])
1891
# What about b/d/e? shouldn't that also get 2 directory entries?
1892
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1893
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1895
def test_bisect_dirblocks(self):
1896
tree, state, expected = self.create_duplicated_dirstate()
1897
self.assertBisectDirBlocks(expected,
1898
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1900
self.assertBisectDirBlocks(expected,
1901
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1902
self.assertBisectDirBlocks(expected,
1903
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1904
self.assertBisectDirBlocks(expected,
1905
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1906
['b/c', 'b/c2', 'b/d', 'b/d2'],
1907
['b/d/e', 'b/d/e2'],
1908
], state, ['', 'b', 'b/d'])
1910
def test_bisect_dirblocks_missing(self):
1911
tree, state, expected = self.create_basic_dirstate()
1912
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1913
state, ['b/d', 'b/e'])
1914
# Files don't show up in this search
1915
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1916
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1917
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1918
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1919
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1921
def test_bisect_recursive_each(self):
1922
tree, state, expected = self.create_basic_dirstate()
1923
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1924
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1925
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
1926
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
1927
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1929
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
1931
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
1935
def test_bisect_recursive_multiple(self):
1936
tree, state, expected = self.create_basic_dirstate()
1937
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
1938
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
1939
state, ['b/d', 'b/d/e'])
1941
def test_bisect_recursive_missing(self):
1942
tree, state, expected = self.create_basic_dirstate()
1943
self.assertBisectRecursive(expected, [], state, ['d'])
1944
self.assertBisectRecursive(expected, [], state, ['b/e'])
1945
self.assertBisectRecursive(expected, [], state, ['g'])
1946
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
1948
def test_bisect_recursive_renamed(self):
1949
tree, state, expected = self.create_renamed_dirstate()
1951
# Looking for either renamed item should find the other
1952
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
1953
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
1954
# Looking in the containing directory should find the rename target,
1955
# and anything in a subdir of the renamed target.
1956
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
1957
'b/d/e', 'b/g', 'h', 'h/e'],
1961
class TestDirstateValidation(TestCaseWithDirState):
1963
def test_validate_correct_dirstate(self):
1964
state = self.create_complex_dirstate()
1967
# and make sure we can also validate with a read lock
1974
def test_dirblock_not_sorted(self):
1975
tree, state, expected = self.create_renamed_dirstate()
1976
state._read_dirblocks_if_needed()
1977
last_dirblock = state._dirblocks[-1]
1978
# we're appending to the dirblock, but this name comes before some of
1979
# the existing names; that's wrong
1980
last_dirblock[1].append(
1981
(('h', 'aaaa', 'a-id'),
1982
[('a', '', 0, False, ''),
1983
('a', '', 0, False, '')]))
1984
e = self.assertRaises(AssertionError,
1986
self.assertContainsRe(str(e), 'not sorted')
1988
def test_dirblock_name_mismatch(self):
1989
tree, state, expected = self.create_renamed_dirstate()
1990
state._read_dirblocks_if_needed()
1991
last_dirblock = state._dirblocks[-1]
1992
# add an entry with the wrong directory name
1993
last_dirblock[1].append(
1995
[('a', '', 0, False, ''),
1996
('a', '', 0, False, '')]))
1997
e = self.assertRaises(AssertionError,
1999
self.assertContainsRe(str(e),
2000
"doesn't match directory name")
2002
def test_dirblock_missing_rename(self):
2003
tree, state, expected = self.create_renamed_dirstate()
2004
state._read_dirblocks_if_needed()
2005
last_dirblock = state._dirblocks[-1]
2006
# make another entry for a-id, without a correct 'r' pointer to
2007
# the real occurrence in the working tree
2008
last_dirblock[1].append(
2009
(('h', 'z', 'a-id'),
2010
[('a', '', 0, False, ''),
2011
('a', '', 0, False, '')]))
2012
e = self.assertRaises(AssertionError,
2014
self.assertContainsRe(str(e),
2015
'file a-id is absent in row')
2018
class TestDirstateTreeReference(TestCaseWithDirState):
2020
def test_reference_revision_is_none(self):
2021
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2022
subtree = self.make_branch_and_tree('tree/subtree',
2023
format='dirstate-with-subtree')
2024
subtree.set_root_id('subtree')
2025
tree.add_reference(subtree)
2027
state = dirstate.DirState.from_tree(tree, 'dirstate')
2028
key = ('', 'subtree', 'subtree')
2029
expected = ('', [(key,
2030
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2033
self.assertEqual(expected, state._find_block(key))
2038
class TestDiscardMergeParents(TestCaseWithDirState):
2040
def test_discard_no_parents(self):
2041
# This should be a no-op
2042
state = self.create_empty_dirstate()
2043
self.addCleanup(state.unlock)
2044
state._discard_merge_parents()
2047
def test_discard_one_parent(self):
2049
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2050
root_entry_direntry = ('', '', 'a-root-value'), [
2051
('d', '', 0, False, packed_stat),
2052
('d', '', 0, False, packed_stat),
2055
dirblocks.append(('', [root_entry_direntry]))
2056
dirblocks.append(('', []))
2058
state = self.create_empty_dirstate()
2059
self.addCleanup(state.unlock)
2060
state._set_data(['parent-id'], dirblocks[:])
2063
state._discard_merge_parents()
2065
self.assertEqual(dirblocks, state._dirblocks)
2067
def test_discard_simple(self):
2069
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2070
root_entry_direntry = ('', '', 'a-root-value'), [
2071
('d', '', 0, False, packed_stat),
2072
('d', '', 0, False, packed_stat),
2073
('d', '', 0, False, packed_stat),
2075
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2076
('d', '', 0, False, packed_stat),
2077
('d', '', 0, False, packed_stat),
2080
dirblocks.append(('', [root_entry_direntry]))
2081
dirblocks.append(('', []))
2083
state = self.create_empty_dirstate()
2084
self.addCleanup(state.unlock)
2085
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2088
# This should strip of the extra column
2089
state._discard_merge_parents()
2091
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2092
self.assertEqual(expected_dirblocks, state._dirblocks)
2094
def test_discard_absent(self):
2095
"""If entries are only in a merge, discard should remove the entries"""
2096
null_stat = dirstate.DirState.NULLSTAT
2097
present_dir = ('d', '', 0, False, null_stat)
2098
present_file = ('f', '', 0, False, null_stat)
2099
absent = dirstate.DirState.NULL_PARENT_DETAILS
2100
root_key = ('', '', 'a-root-value')
2101
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2102
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2103
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2104
('', [(file_in_merged_key,
2105
[absent, absent, present_file]),
2107
[present_file, present_file, present_file]),
2111
state = self.create_empty_dirstate()
2112
self.addCleanup(state.unlock)
2113
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2116
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2117
('', [(file_in_root_key,
2118
[present_file, present_file]),
2121
state._discard_merge_parents()
2123
self.assertEqual(exp_dirblocks, state._dirblocks)
2125
def test_discard_renamed(self):
2126
null_stat = dirstate.DirState.NULLSTAT
2127
present_dir = ('d', '', 0, False, null_stat)
2128
present_file = ('f', '', 0, False, null_stat)
2129
absent = dirstate.DirState.NULL_PARENT_DETAILS
2130
root_key = ('', '', 'a-root-value')
2131
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2132
# Renamed relative to parent
2133
file_rename_s_key = ('', 'file-s', 'b-file-id')
2134
file_rename_t_key = ('', 'file-t', 'b-file-id')
2135
# And one that is renamed between the parents, but absent in this
2136
key_in_1 = ('', 'file-in-1', 'c-file-id')
2137
key_in_2 = ('', 'file-in-2', 'c-file-id')
2140
('', [(root_key, [present_dir, present_dir, present_dir])]),
2142
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2144
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2146
[present_file, present_file, present_file]),
2148
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2150
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2154
('', [(root_key, [present_dir, present_dir])]),
2155
('', [(key_in_1, [absent, present_file]),
2156
(file_in_root_key, [present_file, present_file]),
2157
(file_rename_t_key, [present_file, absent]),
2160
state = self.create_empty_dirstate()
2161
self.addCleanup(state.unlock)
2162
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2165
state._discard_merge_parents()
2167
self.assertEqual(exp_dirblocks, state._dirblocks)
2169
def test_discard_all_subdir(self):
2170
null_stat = dirstate.DirState.NULLSTAT
2171
present_dir = ('d', '', 0, False, null_stat)
2172
present_file = ('f', '', 0, False, null_stat)
2173
absent = dirstate.DirState.NULL_PARENT_DETAILS
2174
root_key = ('', '', 'a-root-value')
2175
subdir_key = ('', 'sub', 'dir-id')
2176
child1_key = ('sub', 'child1', 'child1-id')
2177
child2_key = ('sub', 'child2', 'child2-id')
2178
child3_key = ('sub', 'child3', 'child3-id')
2181
('', [(root_key, [present_dir, present_dir, present_dir])]),
2182
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2183
('sub', [(child1_key, [absent, absent, present_file]),
2184
(child2_key, [absent, absent, present_file]),
2185
(child3_key, [absent, absent, present_file]),
2189
('', [(root_key, [present_dir, present_dir])]),
2190
('', [(subdir_key, [present_dir, present_dir])]),
2193
state = self.create_empty_dirstate()
2194
self.addCleanup(state.unlock)
2195
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2198
state._discard_merge_parents()
2200
self.assertEqual(exp_dirblocks, state._dirblocks)
2203
class Test_InvEntryToDetails(TestCaseWithDirState):
2205
def assertDetails(self, expected, inv_entry):
2206
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2207
self.assertEqual(expected, details)
2208
# details should always allow join() and always be a plain str when
2210
(minikind, fingerprint, size, executable, tree_data) = details
2211
self.assertIsInstance(minikind, str)
2212
self.assertIsInstance(fingerprint, str)
2213
self.assertIsInstance(tree_data, str)
2215
def test_unicode_symlink(self):
2216
# In general, the code base doesn't support a target that contains
2217
# non-ascii characters. So we just assert tha
2218
inv_entry = inventory.InventoryLink('link-file-id', 'name',
2220
inv_entry.revision = 'link-revision-id'
2221
inv_entry.symlink_target = u'link-target'
2222
details = self.assertDetails(('l', 'link-target', 0, False,
2223
'link-revision-id'), inv_entry)