201
def create_basic_dirstate(self):
202
"""Create a dirstate with a few files and directories.
212
tree = self.make_branch_and_tree('tree')
213
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
214
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
215
self.build_tree(['tree/' + p for p in paths])
216
tree.set_root_id('TREE_ROOT')
217
tree.add([p.rstrip('/') for p in paths], file_ids)
218
tree.commit('initial', rev_id='rev-1')
219
revision_id = 'rev-1'
220
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
221
t = self.get_transport('tree')
222
a_text = t.get_bytes('a')
223
a_sha = osutils.sha_string(a_text)
225
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
226
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
227
c_text = t.get_bytes('b/c')
228
c_sha = osutils.sha_string(c_text)
230
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
231
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
232
e_text = t.get_bytes('b/d/e')
233
e_sha = osutils.sha_string(e_text)
235
b_c_text = t.get_bytes('b-c')
236
b_c_sha = osutils.sha_string(b_c_text)
237
b_c_len = len(b_c_text)
238
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
239
f_text = t.get_bytes('f')
240
f_sha = osutils.sha_string(f_text)
242
null_stat = dirstate.DirState.NULLSTAT
244
'':(('', '', 'TREE_ROOT'), [
245
('d', '', 0, False, null_stat),
246
('d', '', 0, False, revision_id),
248
'a':(('', 'a', 'a-id'), [
249
('f', '', 0, False, null_stat),
250
('f', a_sha, a_len, False, revision_id),
252
'b':(('', 'b', 'b-id'), [
253
('d', '', 0, False, null_stat),
254
('d', '', 0, False, revision_id),
256
'b/c':(('b', 'c', 'c-id'), [
257
('f', '', 0, False, null_stat),
258
('f', c_sha, c_len, False, revision_id),
260
'b/d':(('b', 'd', 'd-id'), [
261
('d', '', 0, False, null_stat),
262
('d', '', 0, False, revision_id),
264
'b/d/e':(('b/d', 'e', 'e-id'), [
265
('f', '', 0, False, null_stat),
266
('f', e_sha, e_len, False, revision_id),
268
'b-c':(('', 'b-c', 'b-c-id'), [
269
('f', '', 0, False, null_stat),
270
('f', b_c_sha, b_c_len, False, revision_id),
272
'f':(('', 'f', 'f-id'), [
273
('f', '', 0, False, null_stat),
274
('f', f_sha, f_len, False, revision_id),
277
state = dirstate.DirState.from_tree(tree, 'dirstate')
282
# Use a different object, to make sure nothing is pre-cached in memory.
283
state = dirstate.DirState.on_file('dirstate')
285
self.addCleanup(state.unlock)
286
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
287
state._dirblock_state)
288
# This is code is only really tested if we actually have to make more
289
# than one read, so set the page size to something smaller.
290
# We want it to contain about 2.2 records, so that we have a couple
291
# records that we can read per attempt
292
state._bisect_page_size = 200
293
return tree, state, expected
295
def create_duplicated_dirstate(self):
296
"""Create a dirstate with a deleted and added entries.
298
This grabs a basic_dirstate, and then removes and re adds every entry
301
tree, state, expected = self.create_basic_dirstate()
302
# Now we will just remove and add every file so we get an extra entry
303
# per entry. Unversion in reverse order so we handle subdirs
304
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
305
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
306
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
308
# Update the expected dictionary.
309
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
310
orig = expected[path]
312
# This record was deleted in the current tree
313
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
315
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
316
# And didn't exist in the basis tree
317
expected[path2] = (new_key, [orig[1][0],
318
dirstate.DirState.NULL_PARENT_DETAILS])
320
# We will replace the 'dirstate' file underneath 'state', but that is
321
# okay as lock as we unlock 'state' first.
324
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
330
# But we need to leave state in a read-lock because we already have
331
# a cleanup scheduled
333
return tree, state, expected
335
def create_renamed_dirstate(self):
336
"""Create a dirstate with a few internal renames.
338
This takes the basic dirstate, and moves the paths around.
340
tree, state, expected = self.create_basic_dirstate()
342
tree.rename_one('a', 'b/g')
344
tree.rename_one('b/d', 'h')
346
old_a = expected['a']
347
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
348
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
349
('r', 'a', 0, False, '')])
350
old_d = expected['b/d']
351
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
352
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
353
('r', 'b/d', 0, False, '')])
355
old_e = expected['b/d/e']
356
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
358
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
359
('r', 'b/d/e', 0, False, '')])
363
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
370
return tree, state, expected
373
188
class TestTreeToDirState(TestCaseWithDirState):
568
def test_can_save_in_read_lock(self):
569
self.build_tree(['a-file'])
570
state = dirstate.DirState.initialize('dirstate')
572
# No stat and no sha1 sum.
573
state.add('a-file', 'a-file-id', 'file', None, '')
578
# Now open in readonly mode
579
state = dirstate.DirState.on_file('dirstate')
582
entry = state._get_entry(0, path_utf8='a-file')
583
# The current size should be 0 (default)
584
self.assertEqual(0, entry[1][0][2])
585
# We should have a real entry.
586
self.assertNotEqual((None, None), entry)
587
# Make sure everything is old enough
588
state._sha_cutoff_time()
589
state._cutoff_time += 10
590
# Change the file length
591
self.build_tree_contents([('a-file', 'shorter')])
592
sha1sum = dirstate.update_entry(state, entry, 'a-file',
594
# new file, no cached sha:
595
self.assertEqual(None, sha1sum)
597
# The dirblock has been updated
598
self.assertEqual(7, entry[1][0][2])
599
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
600
state._dirblock_state)
603
# Now, since we are the only one holding a lock, we should be able
604
# to save and have it written to disk
609
# Re-open the file, and ensure that the state has been updated.
610
state = dirstate.DirState.on_file('dirstate')
613
entry = state._get_entry(0, path_utf8='a-file')
614
self.assertEqual(7, entry[1][0][2])
618
def test_save_fails_quietly_if_locked(self):
619
"""If dirstate is locked, save will fail without complaining."""
620
self.build_tree(['a-file'])
621
state = dirstate.DirState.initialize('dirstate')
623
# No stat and no sha1 sum.
624
state.add('a-file', 'a-file-id', 'file', None, '')
629
state = dirstate.DirState.on_file('dirstate')
632
entry = state._get_entry(0, path_utf8='a-file')
633
sha1sum = dirstate.update_entry(state, entry, 'a-file',
636
self.assertEqual(None, sha1sum)
637
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
638
state._dirblock_state)
640
# Now, before we try to save, grab another dirstate, and take out a
642
# TODO: jam 20070315 Ideally this would be locked by another
643
# process. To make sure the file is really OS locked.
644
state2 = dirstate.DirState.on_file('dirstate')
647
# This won't actually write anything, because it couldn't grab
648
# a write lock. But it shouldn't raise an error, either.
649
# TODO: jam 20070315 We should probably distinguish between
650
# being dirty because of 'update_entry'. And dirty
651
# because of real modification. So that save() *does*
652
# raise a real error if it fails when we have real
660
# The file on disk should not be modified.
661
state = dirstate.DirState.on_file('dirstate')
664
entry = state._get_entry(0, path_utf8='a-file')
665
self.assertEqual('', entry[1][0][1])
669
def test_save_refuses_if_changes_aborted(self):
670
self.build_tree(['a-file', 'a-dir/'])
671
state = dirstate.DirState.initialize('dirstate')
673
# No stat and no sha1 sum.
674
state.add('a-file', 'a-file-id', 'file', None, '')
679
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
681
('', [(('', '', 'TREE_ROOT'),
682
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
683
('', [(('', 'a-file', 'a-file-id'),
684
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
687
state = dirstate.DirState.on_file('dirstate')
690
state._read_dirblocks_if_needed()
691
self.assertEqual(expected_blocks, state._dirblocks)
693
# Now modify the state, but mark it as inconsistent
694
state.add('a-dir', 'a-dir-id', 'directory', None, '')
695
state._changes_aborted = True
700
state = dirstate.DirState.on_file('dirstate')
703
state._read_dirblocks_if_needed()
704
self.assertEqual(expected_blocks, state._dirblocks)
709
351
class TestDirStateInitialize(TestCaseWithDirState):
758
398
# This will unlock it
759
399
self.check_state_with_reopen(expected_result, state)
761
def test_set_state_from_inventory_preserves_hashcache(self):
762
# https://bugs.launchpad.net/bzr/+bug/146176
763
# set_state_from_inventory should preserve the stat and hash value for
764
# workingtree files that are not changed by the inventory.
766
tree = self.make_branch_and_tree('.')
767
# depends on the default format using dirstate...
770
# make a dirstate with some valid hashcache data
771
# file on disk, but that's not needed for this test
772
foo_contents = 'contents of foo'
773
self.build_tree_contents([('foo', foo_contents)])
774
tree.add('foo', 'foo-id')
776
foo_stat = os.stat('foo')
777
foo_packed = dirstate.pack_stat(foo_stat)
778
foo_sha = osutils.sha_string(foo_contents)
779
foo_size = len(foo_contents)
781
# should not be cached yet, because the file's too fresh
783
(('', 'foo', 'foo-id',),
784
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
785
tree._dirstate._get_entry(0, 'foo-id'))
786
# poke in some hashcache information - it wouldn't normally be
787
# stored because it's too fresh
788
tree._dirstate.update_minimal(
789
('', 'foo', 'foo-id'),
790
'f', False, foo_sha, foo_packed, foo_size, 'foo')
791
# now should be cached
793
(('', 'foo', 'foo-id',),
794
[('f', foo_sha, foo_size, False, foo_packed)]),
795
tree._dirstate._get_entry(0, 'foo-id'))
797
# extract the inventory, and add something to it
798
inv = tree._get_inventory()
799
# should see the file we poked in...
800
self.assertTrue(inv.has_id('foo-id'))
801
self.assertTrue(inv.has_filename('foo'))
802
inv.add_path('bar', 'file', 'bar-id')
803
tree._dirstate._validate()
804
# this used to cause it to lose its hashcache
805
tree._dirstate.set_state_from_inventory(inv)
806
tree._dirstate._validate()
812
# now check that the state still has the original hashcache value
813
state = tree._dirstate
815
foo_tuple = state._get_entry(0, path_utf8='foo')
817
(('', 'foo', 'foo-id',),
818
[('f', foo_sha, len(foo_contents), False,
819
dirstate.pack_stat(foo_stat))]),
824
def test_set_state_from_inventory_mixed_paths(self):
825
tree1 = self.make_branch_and_tree('tree1')
826
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
827
'tree1/a/b/foo', 'tree1/a-b/bar'])
830
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
831
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
832
tree1.commit('rev1', rev_id='rev1')
833
root_id = tree1.get_root_id()
834
inv = tree1.inventory
837
expected_result1 = [('', '', root_id, 'd'),
838
('', 'a', 'a-id', 'd'),
839
('', 'a-b', 'a-b-id', 'd'),
840
('a', 'b', 'b-id', 'd'),
841
('a/b', 'foo', 'foo-id', 'f'),
842
('a-b', 'bar', 'bar-id', 'f'),
844
expected_result2 = [('', '', root_id, 'd'),
845
('', 'a', 'a-id', 'd'),
846
('', 'a-b', 'a-b-id', 'd'),
847
('a-b', 'bar', 'bar-id', 'f'),
849
state = dirstate.DirState.initialize('dirstate')
851
state.set_state_from_inventory(inv)
853
for entry in state._iter_entries():
854
values.append(entry[0] + entry[1][0][:1])
855
self.assertEqual(expected_result1, values)
857
state.set_state_from_inventory(inv)
859
for entry in state._iter_entries():
860
values.append(entry[0] + entry[1][0][:1])
861
self.assertEqual(expected_result2, values)
865
401
def test_set_path_id_no_parents(self):
866
402
"""The id of a path can be changed trivally with no parents."""
867
403
state = dirstate.DirState.initialize('dirstate')
869
405
# check precondition to be sure the state does change appropriately.
870
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
871
self.assertEqual([root_entry], list(state._iter_entries()))
872
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
873
self.assertEqual(root_entry,
874
state._get_entry(0, fileid_utf8='TREE_ROOT'))
875
self.assertEqual((None, None),
876
state._get_entry(0, fileid_utf8='second-root-id'))
877
state.set_path_id('', 'second-root-id')
878
new_root_entry = (('', '', 'second-root-id'),
879
[('d', '', 0, False, 'x'*32)])
880
expected_rows = [new_root_entry]
881
self.assertEqual(expected_rows, list(state._iter_entries()))
882
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
883
self.assertEqual(new_root_entry,
884
state._get_entry(0, fileid_utf8='second-root-id'))
885
self.assertEqual((None, None),
886
state._get_entry(0, fileid_utf8='TREE_ROOT'))
887
# should work across save too
891
state = dirstate.DirState.on_file('dirstate')
895
self.assertEqual(expected_rows, list(state._iter_entries()))
899
def test_set_path_id_with_parents(self):
900
"""Set the root file id in a dirstate with parents"""
901
mt = self.make_branch_and_tree('mt')
902
# in case the default tree format uses a different root id
903
mt.set_root_id('TREE_ROOT')
904
mt.commit('foo', rev_id='parent-revid')
905
rt = mt.branch.repository.revision_tree('parent-revid')
906
state = dirstate.DirState.initialize('dirstate')
909
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
910
root_entry = (('', '', 'TREE_ROOT'),
911
[('d', '', 0, False, 'x'*32),
912
('d', '', 0, False, 'parent-revid')])
913
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
914
self.assertEqual(root_entry,
915
state._get_entry(0, fileid_utf8='TREE_ROOT'))
916
self.assertEqual((None, None),
917
state._get_entry(0, fileid_utf8='Asecond-root-id'))
918
state.set_path_id('', 'Asecond-root-id')
920
# now see that it is what we expected
921
old_root_entry = (('', '', 'TREE_ROOT'),
922
[('a', '', 0, False, ''),
923
('d', '', 0, False, 'parent-revid')])
924
new_root_entry = (('', '', 'Asecond-root-id'),
925
[('d', '', 0, False, ''),
926
('a', '', 0, False, '')])
927
expected_rows = [new_root_entry, old_root_entry]
929
self.assertEqual(expected_rows, list(state._iter_entries()))
930
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
931
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
932
self.assertEqual((None, None),
933
state._get_entry(0, fileid_utf8='TREE_ROOT'))
934
self.assertEqual(old_root_entry,
935
state._get_entry(1, fileid_utf8='TREE_ROOT'))
936
self.assertEqual(new_root_entry,
937
state._get_entry(0, fileid_utf8='Asecond-root-id'))
938
self.assertEqual((None, None),
939
state._get_entry(1, fileid_utf8='Asecond-root-id'))
940
# should work across save too
944
# now flush & check we get the same
945
state = dirstate.DirState.on_file('dirstate')
949
self.assertEqual(expected_rows, list(state._iter_entries()))
952
# now change within an existing file-backed state
956
state.set_path_id('', 'tree-root-2')
407
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
408
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
409
list(state._iter_entries()))
410
state.set_path_id('', 'foobarbaz')
412
(('', '', 'foobarbaz'), [('d', '', 0, False,
413
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
414
self.assertEqual(expected_rows, list(state._iter_entries()))
415
# should work across save too
419
state = dirstate.DirState.on_file('dirstate')
422
self.assertEqual(expected_rows, list(state._iter_entries()))
1506
class TestIterChildEntries(TestCaseWithDirState):
1508
def create_dirstate_with_two_trees(self):
1509
"""This dirstate contains multiple files and directories.
1519
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1521
Notice that a/e is an empty directory.
1523
There is one parent tree, which has the same shape with the following variations:
1524
b/g in the parent is gone.
1525
b/h in the parent has a different id
1526
b/i is new in the parent
1527
c is renamed to b/j in the parent
1529
:return: The dirstate, still write-locked.
1531
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1532
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1533
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1534
root_entry = ('', '', 'a-root-value'), [
1535
('d', '', 0, False, packed_stat),
1536
('d', '', 0, False, 'parent-revid'),
1538
a_entry = ('', 'a', 'a-dir'), [
1539
('d', '', 0, False, packed_stat),
1540
('d', '', 0, False, 'parent-revid'),
1542
b_entry = ('', 'b', 'b-dir'), [
1543
('d', '', 0, False, packed_stat),
1544
('d', '', 0, False, 'parent-revid'),
1546
c_entry = ('', 'c', 'c-file'), [
1547
('f', null_sha, 10, False, packed_stat),
1548
('r', 'b/j', 0, False, ''),
1550
d_entry = ('', 'd', 'd-file'), [
1551
('f', null_sha, 20, False, packed_stat),
1552
('f', 'd', 20, False, 'parent-revid'),
1554
e_entry = ('a', 'e', 'e-dir'), [
1555
('d', '', 0, False, packed_stat),
1556
('d', '', 0, False, 'parent-revid'),
1558
f_entry = ('a', 'f', 'f-file'), [
1559
('f', null_sha, 30, False, packed_stat),
1560
('f', 'f', 20, False, 'parent-revid'),
1562
g_entry = ('b', 'g', 'g-file'), [
1563
('f', null_sha, 30, False, packed_stat),
1564
NULL_PARENT_DETAILS,
1566
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1567
('f', null_sha, 40, False, packed_stat),
1568
NULL_PARENT_DETAILS,
1570
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1571
NULL_PARENT_DETAILS,
1572
('f', 'h', 20, False, 'parent-revid'),
1574
i_entry = ('b', 'i', 'i-file'), [
1575
NULL_PARENT_DETAILS,
1576
('f', 'h', 20, False, 'parent-revid'),
1578
j_entry = ('b', 'j', 'c-file'), [
1579
('r', 'c', 0, False, ''),
1580
('f', 'j', 20, False, 'parent-revid'),
1583
dirblocks.append(('', [root_entry]))
1584
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1585
dirblocks.append(('a', [e_entry, f_entry]))
1586
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1587
state = dirstate.DirState.initialize('dirstate')
1590
state._set_data(['parent'], dirblocks)
1594
return state, dirblocks
1596
def test_iter_children_b(self):
1597
state, dirblocks = self.create_dirstate_with_two_trees()
1598
self.addCleanup(state.unlock)
1599
expected_result = []
1600
expected_result.append(dirblocks[3][1][2]) # h2
1601
expected_result.append(dirblocks[3][1][3]) # i
1602
expected_result.append(dirblocks[3][1][4]) # j
1603
self.assertEqual(expected_result,
1604
list(state._iter_child_entries(1, 'b')))
1606
def test_iter_child_root(self):
1607
state, dirblocks = self.create_dirstate_with_two_trees()
1608
self.addCleanup(state.unlock)
1609
expected_result = []
1610
expected_result.append(dirblocks[1][1][0]) # a
1611
expected_result.append(dirblocks[1][1][1]) # b
1612
expected_result.append(dirblocks[1][1][3]) # d
1613
expected_result.append(dirblocks[2][1][0]) # e
1614
expected_result.append(dirblocks[2][1][1]) # f
1615
expected_result.append(dirblocks[3][1][2]) # h2
1616
expected_result.append(dirblocks[3][1][3]) # i
1617
expected_result.append(dirblocks[3][1][4]) # j
1618
self.assertEqual(expected_result,
1619
list(state._iter_child_entries(1, '')))
1622
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1623
"""Test that DirState adds entries in the right order."""
1625
def test_add_sorting(self):
1626
"""Add entries in lexicographical order, we get path sorted order.
1628
This tests it to a depth of 4, to make sure we don't just get it right
1629
at a single depth. 'a/a' should come before 'a-a', even though it
1630
doesn't lexicographically.
1632
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1633
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1636
state = dirstate.DirState.initialize('dirstate')
1637
self.addCleanup(state.unlock)
1639
fake_stat = os.stat('dirstate')
1641
d_id = d.replace('/', '_')+'-id'
1642
file_path = d + '/f'
1643
file_id = file_path.replace('/', '_')+'-id'
1644
state.add(d, d_id, 'directory', fake_stat, null_sha)
1645
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1647
expected = ['', '', 'a',
1648
'a/a', 'a/a/a', 'a/a/a/a',
1649
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1651
split = lambda p:p.split('/')
1652
self.assertEqual(sorted(expected, key=split), expected)
1653
dirblock_names = [d[0] for d in state._dirblocks]
1654
self.assertEqual(expected, dirblock_names)
1656
def test_set_parent_trees_correct_order(self):
1657
"""After calling set_parent_trees() we should maintain the order."""
1658
dirs = ['a', 'a-a', 'a/a']
1660
state = dirstate.DirState.initialize('dirstate')
1661
self.addCleanup(state.unlock)
1663
fake_stat = os.stat('dirstate')
1665
d_id = d.replace('/', '_')+'-id'
1666
file_path = d + '/f'
1667
file_id = file_path.replace('/', '_')+'-id'
1668
state.add(d, d_id, 'directory', fake_stat, null_sha)
1669
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1671
expected = ['', '', 'a', 'a/a', 'a-a']
1672
dirblock_names = [d[0] for d in state._dirblocks]
1673
self.assertEqual(expected, dirblock_names)
1675
# *really* cheesy way to just get an empty tree
1676
repo = self.make_repository('repo')
1677
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1678
state.set_parent_trees([('null:', empty_tree)], [])
1680
dirblock_names = [d[0] for d in state._dirblocks]
1681
self.assertEqual(expected, dirblock_names)
1684
class InstrumentedDirState(dirstate.DirState):
1685
"""An DirState with instrumented sha1 functionality."""
1687
def __init__(self, path, sha1_provider):
1688
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1689
self._time_offset = 0
1691
# member is dynamically set in DirState.__init__ to turn on trace
1692
self._sha1_provider = sha1_provider
1693
self._sha1_file = self._sha1_file_and_log
1695
def _sha_cutoff_time(self):
1696
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1697
self._cutoff_time = timestamp + self._time_offset
1699
def _sha1_file_and_log(self, abspath):
1700
self._log.append(('sha1', abspath))
1701
return self._sha1_provider.sha1(abspath)
1703
def _read_link(self, abspath, old_link):
1704
self._log.append(('read_link', abspath, old_link))
1705
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1707
def _lstat(self, abspath, entry):
1708
self._log.append(('lstat', abspath))
1709
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1711
def _is_executable(self, mode, old_executable):
1712
self._log.append(('is_exec', mode, old_executable))
1713
return super(InstrumentedDirState, self)._is_executable(mode,
1716
def adjust_time(self, secs):
1717
"""Move the clock forward or back.
1719
:param secs: The amount to adjust the clock by. Positive values make it
1720
seem as if we are in the future, negative values make it seem like we
1723
self._time_offset += secs
1724
self._cutoff_time = None
1727
class _FakeStat(object):
1728
"""A class with the same attributes as a real stat result."""
1730
def __init__(self, size, mtime, ctime, dev, ino, mode):
1732
self.st_mtime = mtime
1733
self.st_ctime = ctime
1740
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1741
st.st_ino, st.st_mode)
1744
class TestPackStat(tests.TestCaseWithTransport):
1746
def assertPackStat(self, expected, stat_value):
1747
"""Check the packed and serialized form of a stat value."""
1748
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1750
def test_pack_stat_int(self):
1751
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1752
# Make sure that all parameters have an impact on the packed stat.
1753
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1756
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1757
st.st_mtime = 1172758620
1759
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1760
st.st_ctime = 1172758630
1762
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1765
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1766
st.st_ino = 6499540L
1768
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1769
st.st_mode = 0100744
1771
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1773
def test_pack_stat_float(self):
1774
"""On some platforms mtime and ctime are floats.
1776
Make sure we don't get warnings or errors, and that we ignore changes <
1779
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1780
777L, 6499538L, 0100644)
1781
# These should all be the same as the integer counterparts
1782
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1783
st.st_mtime = 1172758620.0
1785
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1786
st.st_ctime = 1172758630.0
1788
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1789
# fractional seconds are discarded, so no change from above
1790
st.st_mtime = 1172758620.453
1791
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1792
st.st_ctime = 1172758630.228
1793
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1796
class TestBisect(TestCaseWithDirState):
900
class TestBisect(TestCaseWithTransport):
1797
901
"""Test the ability to bisect into the disk format."""
903
def create_basic_dirstate(self):
904
"""Create a dirstate with a few files and directories.
913
tree = self.make_branch_and_tree('tree')
914
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
915
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
916
self.build_tree(['tree/' + p for p in paths])
917
tree.set_root_id('TREE_ROOT')
918
tree.add([p.rstrip('/') for p in paths], file_ids)
919
tree.commit('initial', rev_id='rev-1')
920
revision_id = 'rev-1'
921
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
922
t = self.get_transport().clone('tree')
923
a_text = t.get_bytes('a')
924
a_sha = osutils.sha_string(a_text)
926
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
927
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
928
c_text = t.get_bytes('b/c')
929
c_sha = osutils.sha_string(c_text)
931
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
932
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
933
e_text = t.get_bytes('b/d/e')
934
e_sha = osutils.sha_string(e_text)
936
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
937
f_text = t.get_bytes('f')
938
f_sha = osutils.sha_string(f_text)
940
null_stat = dirstate.DirState.NULLSTAT
942
'':(('', '', 'TREE_ROOT'), [
943
('d', '', 0, False, null_stat),
944
('d', '', 0, False, revision_id),
946
'a':(('', 'a', 'a-id'), [
947
('f', '', 0, False, null_stat),
948
('f', a_sha, a_len, False, revision_id),
950
'b':(('', 'b', 'b-id'), [
951
('d', '', 0, False, null_stat),
952
('d', '', 0, False, revision_id),
954
'b/c':(('b', 'c', 'c-id'), [
955
('f', '', 0, False, null_stat),
956
('f', c_sha, c_len, False, revision_id),
958
'b/d':(('b', 'd', 'd-id'), [
959
('d', '', 0, False, null_stat),
960
('d', '', 0, False, revision_id),
962
'b/d/e':(('b/d', 'e', 'e-id'), [
963
('f', '', 0, False, null_stat),
964
('f', e_sha, e_len, False, revision_id),
966
'f':(('', 'f', 'f-id'), [
967
('f', '', 0, False, null_stat),
968
('f', f_sha, f_len, False, revision_id),
971
state = dirstate.DirState.from_tree(tree, 'dirstate')
976
# Use a different object, to make sure nothing is pre-cached in memory.
977
state = dirstate.DirState.on_file('dirstate')
979
self.addCleanup(state.unlock)
980
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
981
state._dirblock_state)
982
# This is code is only really tested if we actually have to make more
983
# than one read, so set the page size to something smaller.
984
# We want it to contain about 2.2 records, so that we have a couple
985
# records that we can read per attempt
986
state._bisect_page_size = 200
987
return tree, state, expected
989
def create_duplicated_dirstate(self):
990
"""Create a dirstate with a deleted and added entries.
992
This grabs a basic_dirstate, and then removes and re adds every entry
995
tree, state, expected = self.create_basic_dirstate()
996
# Now we will just remove and add every file so we get an extra entry
997
# per entry. Unversion in reverse order so we handle subdirs
998
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
999
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1000
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1002
# Update the expected dictionary.
1003
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1004
orig = expected[path]
1006
# This record was deleted in the current tree
1007
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1009
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1010
# And didn't exist in the basis tree
1011
expected[path2] = (new_key, [orig[1][0],
1012
dirstate.DirState.NULL_PARENT_DETAILS])
1014
# We will replace the 'dirstate' file underneath 'state', but that is
1015
# okay as lock as we unlock 'state' first.
1018
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1024
# But we need to leave state in a read-lock because we already have
1025
# a cleanup scheduled
1027
return tree, state, expected
1029
def create_renamed_dirstate(self):
1030
"""Create a dirstate with a few internal renames.
1032
This takes the basic dirstate, and moves the paths around.
1034
tree, state, expected = self.create_basic_dirstate()
1036
tree.rename_one('a', 'b/g')
1038
tree.rename_one('b/d', 'h')
1040
old_a = expected['a']
1041
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1042
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1043
('r', 'a', 0, False, '')])
1044
old_d = expected['b/d']
1045
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1046
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1047
('r', 'b/d', 0, False, '')])
1049
old_e = expected['b/d/e']
1050
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1052
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1053
('r', 'b/d/e', 0, False, '')])
1057
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1064
return tree, state, expected
1799
1066
def assertBisect(self, expected_map, map_keys, state, paths):
1800
1067
"""Assert that bisecting for paths returns the right result.
2031
1290
'b/d/e', 'b/g', 'h', 'h/e'],
2035
class TestDirstateValidation(TestCaseWithDirState):
2037
def test_validate_correct_dirstate(self):
2038
state = self.create_complex_dirstate()
2041
# and make sure we can also validate with a read lock
2048
def test_dirblock_not_sorted(self):
2049
tree, state, expected = self.create_renamed_dirstate()
2050
state._read_dirblocks_if_needed()
2051
last_dirblock = state._dirblocks[-1]
2052
# we're appending to the dirblock, but this name comes before some of
2053
# the existing names; that's wrong
2054
last_dirblock[1].append(
2055
(('h', 'aaaa', 'a-id'),
2056
[('a', '', 0, False, ''),
2057
('a', '', 0, False, '')]))
2058
e = self.assertRaises(AssertionError,
2060
self.assertContainsRe(str(e), 'not sorted')
2062
def test_dirblock_name_mismatch(self):
2063
tree, state, expected = self.create_renamed_dirstate()
2064
state._read_dirblocks_if_needed()
2065
last_dirblock = state._dirblocks[-1]
2066
# add an entry with the wrong directory name
2067
last_dirblock[1].append(
2069
[('a', '', 0, False, ''),
2070
('a', '', 0, False, '')]))
2071
e = self.assertRaises(AssertionError,
2073
self.assertContainsRe(str(e),
2074
"doesn't match directory name")
2076
def test_dirblock_missing_rename(self):
2077
tree, state, expected = self.create_renamed_dirstate()
2078
state._read_dirblocks_if_needed()
2079
last_dirblock = state._dirblocks[-1]
2080
# make another entry for a-id, without a correct 'r' pointer to
2081
# the real occurrence in the working tree
2082
last_dirblock[1].append(
2083
(('h', 'z', 'a-id'),
2084
[('a', '', 0, False, ''),
2085
('a', '', 0, False, '')]))
2086
e = self.assertRaises(AssertionError,
2088
self.assertContainsRe(str(e),
2089
'file a-id is absent in row')
2092
class TestDirstateTreeReference(TestCaseWithDirState):
2094
def test_reference_revision_is_none(self):
2095
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2096
subtree = self.make_branch_and_tree('tree/subtree',
2097
format='dirstate-with-subtree')
2098
subtree.set_root_id('subtree')
2099
tree.add_reference(subtree)
2101
state = dirstate.DirState.from_tree(tree, 'dirstate')
2102
key = ('', 'subtree', 'subtree')
2103
expected = ('', [(key,
2104
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2107
self.assertEqual(expected, state._find_block(key))
2112
class TestDiscardMergeParents(TestCaseWithDirState):
2114
def test_discard_no_parents(self):
2115
# This should be a no-op
2116
state = self.create_empty_dirstate()
2117
self.addCleanup(state.unlock)
2118
state._discard_merge_parents()
2121
def test_discard_one_parent(self):
2123
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2124
root_entry_direntry = ('', '', 'a-root-value'), [
2125
('d', '', 0, False, packed_stat),
2126
('d', '', 0, False, packed_stat),
2129
dirblocks.append(('', [root_entry_direntry]))
2130
dirblocks.append(('', []))
2132
state = self.create_empty_dirstate()
2133
self.addCleanup(state.unlock)
2134
state._set_data(['parent-id'], dirblocks[:])
2137
state._discard_merge_parents()
2139
self.assertEqual(dirblocks, state._dirblocks)
2141
def test_discard_simple(self):
2143
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2144
root_entry_direntry = ('', '', 'a-root-value'), [
2145
('d', '', 0, False, packed_stat),
2146
('d', '', 0, False, packed_stat),
2147
('d', '', 0, False, packed_stat),
2149
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2150
('d', '', 0, False, packed_stat),
2151
('d', '', 0, False, packed_stat),
2154
dirblocks.append(('', [root_entry_direntry]))
2155
dirblocks.append(('', []))
2157
state = self.create_empty_dirstate()
2158
self.addCleanup(state.unlock)
2159
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2162
# This should strip of the extra column
2163
state._discard_merge_parents()
2165
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2166
self.assertEqual(expected_dirblocks, state._dirblocks)
2168
def test_discard_absent(self):
2169
"""If entries are only in a merge, discard should remove the entries"""
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
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2176
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2177
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2178
('', [(file_in_merged_key,
2179
[absent, absent, present_file]),
2181
[present_file, present_file, present_file]),
2185
state = self.create_empty_dirstate()
2186
self.addCleanup(state.unlock)
2187
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2190
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2191
('', [(file_in_root_key,
2192
[present_file, present_file]),
2195
state._discard_merge_parents()
2197
self.assertEqual(exp_dirblocks, state._dirblocks)
2199
def test_discard_renamed(self):
2200
null_stat = dirstate.DirState.NULLSTAT
2201
present_dir = ('d', '', 0, False, null_stat)
2202
present_file = ('f', '', 0, False, null_stat)
2203
absent = dirstate.DirState.NULL_PARENT_DETAILS
2204
root_key = ('', '', 'a-root-value')
2205
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2206
# Renamed relative to parent
2207
file_rename_s_key = ('', 'file-s', 'b-file-id')
2208
file_rename_t_key = ('', 'file-t', 'b-file-id')
2209
# And one that is renamed between the parents, but absent in this
2210
key_in_1 = ('', 'file-in-1', 'c-file-id')
2211
key_in_2 = ('', 'file-in-2', 'c-file-id')
2214
('', [(root_key, [present_dir, present_dir, present_dir])]),
2216
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2218
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2220
[present_file, present_file, present_file]),
2222
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2224
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2228
('', [(root_key, [present_dir, present_dir])]),
2229
('', [(key_in_1, [absent, present_file]),
2230
(file_in_root_key, [present_file, present_file]),
2231
(file_rename_t_key, [present_file, absent]),
2234
state = self.create_empty_dirstate()
2235
self.addCleanup(state.unlock)
2236
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2239
state._discard_merge_parents()
2241
self.assertEqual(exp_dirblocks, state._dirblocks)
2243
def test_discard_all_subdir(self):
2244
null_stat = dirstate.DirState.NULLSTAT
2245
present_dir = ('d', '', 0, False, null_stat)
2246
present_file = ('f', '', 0, False, null_stat)
2247
absent = dirstate.DirState.NULL_PARENT_DETAILS
2248
root_key = ('', '', 'a-root-value')
2249
subdir_key = ('', 'sub', 'dir-id')
2250
child1_key = ('sub', 'child1', 'child1-id')
2251
child2_key = ('sub', 'child2', 'child2-id')
2252
child3_key = ('sub', 'child3', 'child3-id')
2255
('', [(root_key, [present_dir, present_dir, present_dir])]),
2256
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2257
('sub', [(child1_key, [absent, absent, present_file]),
2258
(child2_key, [absent, absent, present_file]),
2259
(child3_key, [absent, absent, present_file]),
2263
('', [(root_key, [present_dir, present_dir])]),
2264
('', [(subdir_key, [present_dir, present_dir])]),
2267
state = self.create_empty_dirstate()
2268
self.addCleanup(state.unlock)
2269
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2272
state._discard_merge_parents()
2274
self.assertEqual(exp_dirblocks, state._dirblocks)
2277
class Test_InvEntryToDetails(tests.TestCase):
2279
def assertDetails(self, expected, inv_entry):
2280
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2281
self.assertEqual(expected, details)
2282
# details should always allow join() and always be a plain str when
2284
(minikind, fingerprint, size, executable, tree_data) = details
2285
self.assertIsInstance(minikind, str)
2286
self.assertIsInstance(fingerprint, str)
2287
self.assertIsInstance(tree_data, str)
2289
def test_unicode_symlink(self):
2290
inv_entry = inventory.InventoryLink('link-file-id',
2291
u'nam\N{Euro Sign}e',
2293
inv_entry.revision = 'link-revision-id'
2294
target = u'link-targ\N{Euro Sign}t'
2295
inv_entry.symlink_target = target
2296
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2297
'link-revision-id'), inv_entry)
2300
class TestSHA1Provider(tests.TestCaseInTempDir):
2302
def test_sha1provider_is_an_interface(self):
2303
p = dirstate.SHA1Provider()
2304
self.assertRaises(NotImplementedError, p.sha1, "foo")
2305
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2307
def test_defaultsha1provider_sha1(self):
2308
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2309
self.build_tree_contents([('foo', text)])
2310
expected_sha = osutils.sha_string(text)
2311
p = dirstate.DefaultSHA1Provider()
2312
self.assertEqual(expected_sha, p.sha1('foo'))
2314
def test_defaultsha1provider_stat_and_sha1(self):
2315
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2316
self.build_tree_contents([('foo', text)])
2317
expected_sha = osutils.sha_string(text)
2318
p = dirstate.DefaultSHA1Provider()
2319
statvalue, sha1 = p.stat_and_sha1('foo')
2320
self.assertTrue(len(statvalue) >= 10)
2321
self.assertEqual(len(text), statvalue.st_size)
2322
self.assertEqual(expected_sha, sha1)