196
def create_basic_dirstate(self):
197
"""Create a dirstate with a few files and directories.
207
tree = self.make_branch_and_tree('tree')
208
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
209
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
210
self.build_tree(['tree/' + p for p in paths])
211
tree.set_root_id('TREE_ROOT')
212
tree.add([p.rstrip('/') for p in paths], file_ids)
213
tree.commit('initial', rev_id='rev-1')
214
revision_id = 'rev-1'
215
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
216
t = self.get_transport('tree')
217
a_text = t.get_bytes('a')
218
a_sha = osutils.sha_string(a_text)
220
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
221
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
222
c_text = t.get_bytes('b/c')
223
c_sha = osutils.sha_string(c_text)
225
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
226
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
227
e_text = t.get_bytes('b/d/e')
228
e_sha = osutils.sha_string(e_text)
230
b_c_text = t.get_bytes('b-c')
231
b_c_sha = osutils.sha_string(b_c_text)
232
b_c_len = len(b_c_text)
233
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
234
f_text = t.get_bytes('f')
235
f_sha = osutils.sha_string(f_text)
237
null_stat = dirstate.DirState.NULLSTAT
239
'':(('', '', 'TREE_ROOT'), [
240
('d', '', 0, False, null_stat),
241
('d', '', 0, False, revision_id),
243
'a':(('', 'a', 'a-id'), [
244
('f', '', 0, False, null_stat),
245
('f', a_sha, a_len, False, revision_id),
247
'b':(('', 'b', 'b-id'), [
248
('d', '', 0, False, null_stat),
249
('d', '', 0, False, revision_id),
251
'b/c':(('b', 'c', 'c-id'), [
252
('f', '', 0, False, null_stat),
253
('f', c_sha, c_len, False, revision_id),
255
'b/d':(('b', 'd', 'd-id'), [
256
('d', '', 0, False, null_stat),
257
('d', '', 0, False, revision_id),
259
'b/d/e':(('b/d', 'e', 'e-id'), [
260
('f', '', 0, False, null_stat),
261
('f', e_sha, e_len, False, revision_id),
263
'b-c':(('', 'b-c', 'b-c-id'), [
264
('f', '', 0, False, null_stat),
265
('f', b_c_sha, b_c_len, False, revision_id),
267
'f':(('', 'f', 'f-id'), [
268
('f', '', 0, False, null_stat),
269
('f', f_sha, f_len, False, revision_id),
272
state = dirstate.DirState.from_tree(tree, 'dirstate')
277
# Use a different object, to make sure nothing is pre-cached in memory.
278
state = dirstate.DirState.on_file('dirstate')
280
self.addCleanup(state.unlock)
281
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
282
state._dirblock_state)
283
# This is code is only really tested if we actually have to make more
284
# than one read, so set the page size to something smaller.
285
# We want it to contain about 2.2 records, so that we have a couple
286
# records that we can read per attempt
287
state._bisect_page_size = 200
288
return tree, state, expected
290
def create_duplicated_dirstate(self):
291
"""Create a dirstate with a deleted and added entries.
293
This grabs a basic_dirstate, and then removes and re adds every entry
296
tree, state, expected = self.create_basic_dirstate()
297
# Now we will just remove and add every file so we get an extra entry
298
# per entry. Unversion in reverse order so we handle subdirs
299
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
300
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
301
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
303
# Update the expected dictionary.
304
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
305
orig = expected[path]
307
# This record was deleted in the current tree
308
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
310
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
311
# And didn't exist in the basis tree
312
expected[path2] = (new_key, [orig[1][0],
313
dirstate.DirState.NULL_PARENT_DETAILS])
315
# We will replace the 'dirstate' file underneath 'state', but that is
316
# okay as lock as we unlock 'state' first.
319
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
325
# But we need to leave state in a read-lock because we already have
326
# a cleanup scheduled
328
return tree, state, expected
330
def create_renamed_dirstate(self):
331
"""Create a dirstate with a few internal renames.
333
This takes the basic dirstate, and moves the paths around.
335
tree, state, expected = self.create_basic_dirstate()
337
tree.rename_one('a', 'b/g')
339
tree.rename_one('b/d', 'h')
341
old_a = expected['a']
342
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
343
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
344
('r', 'a', 0, False, '')])
345
old_d = expected['b/d']
346
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
347
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
348
('r', 'b/d', 0, False, '')])
350
old_e = expected['b/d/e']
351
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
353
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
354
('r', 'b/d/e', 0, False, '')])
358
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
365
return tree, state, expected
368
192
class TestTreeToDirState(TestCaseWithDirState):
576
def test_can_save_in_read_lock(self):
577
state = self.create_updated_dirstate()
579
entry = state._get_entry(0, path_utf8='a-file')
580
# The current size should be 0 (default)
581
self.assertEqual(0, entry[1][0][2])
582
# We should have a real entry.
583
self.assertNotEqual((None, None), entry)
584
# Set the cutoff-time into the future, so things look cacheable
585
state._sha_cutoff_time()
586
state._cutoff_time += 10.0
587
st = os.lstat('a-file')
588
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
589
# We updated the current sha1sum because the file is cacheable
590
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
593
# The dirblock has been updated
594
self.assertEqual(st.st_size, entry[1][0][2])
595
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
596
state._dirblock_state)
599
# Now, since we are the only one holding a lock, we should be able
600
# to save and have it written to disk
605
# Re-open the file, and ensure that the state has been updated.
606
state = dirstate.DirState.on_file('dirstate')
609
entry = state._get_entry(0, path_utf8='a-file')
610
self.assertEqual(st.st_size, entry[1][0][2])
614
def test_save_fails_quietly_if_locked(self):
615
"""If dirstate is locked, save will fail without complaining."""
616
state = self.create_updated_dirstate()
618
entry = state._get_entry(0, path_utf8='a-file')
619
# No cached sha1 yet.
620
self.assertEqual('', entry[1][0][1])
621
# Set the cutoff-time into the future, so things look cacheable
622
state._sha_cutoff_time()
623
state._cutoff_time += 10.0
624
st = os.lstat('a-file')
625
sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
626
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
628
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
629
state._dirblock_state)
631
# Now, before we try to save, grab another dirstate, and take out a
633
# TODO: jam 20070315 Ideally this would be locked by another
634
# process. To make sure the file is really OS locked.
635
state2 = dirstate.DirState.on_file('dirstate')
638
# This won't actually write anything, because it couldn't grab
639
# a write lock. But it shouldn't raise an error, either.
640
# TODO: jam 20070315 We should probably distinguish between
641
# being dirty because of 'update_entry'. And dirty
642
# because of real modification. So that save() *does*
643
# raise a real error if it fails when we have real
651
# The file on disk should not be modified.
652
state = dirstate.DirState.on_file('dirstate')
655
entry = state._get_entry(0, path_utf8='a-file')
656
self.assertEqual('', entry[1][0][1])
660
def test_save_refuses_if_changes_aborted(self):
661
self.build_tree(['a-file', 'a-dir/'])
662
state = dirstate.DirState.initialize('dirstate')
664
# No stat and no sha1 sum.
665
state.add('a-file', 'a-file-id', 'file', None, '')
670
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
672
('', [(('', '', 'TREE_ROOT'),
673
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
674
('', [(('', 'a-file', 'a-file-id'),
675
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
678
state = dirstate.DirState.on_file('dirstate')
681
state._read_dirblocks_if_needed()
682
self.assertEqual(expected_blocks, state._dirblocks)
684
# Now modify the state, but mark it as inconsistent
685
state.add('a-dir', 'a-dir-id', 'directory', None, '')
686
state._changes_aborted = True
691
state = dirstate.DirState.on_file('dirstate')
694
state._read_dirblocks_if_needed()
695
self.assertEqual(expected_blocks, state._dirblocks)
700
380
class TestDirStateInitialize(TestCaseWithDirState):
767
427
# This will unlock it
768
428
self.check_state_with_reopen(expected_result, state)
770
def test_set_state_from_scratch_no_parents(self):
771
tree1, revid1 = self.make_minimal_tree()
772
inv = tree1.inventory
773
root_id = inv.path2id('')
774
expected_result = [], [
775
(('', '', root_id), [
776
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
777
state = dirstate.DirState.initialize('dirstate')
779
state.set_state_from_scratch(inv, [], [])
780
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
782
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
783
state._dirblock_state)
788
# This will unlock it
789
self.check_state_with_reopen(expected_result, state)
791
def test_set_state_from_scratch_identical_parent(self):
792
tree1, revid1 = self.make_minimal_tree()
793
inv = tree1.inventory
794
root_id = inv.path2id('')
795
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
796
d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
797
parent_entry = ('d', '', 0, False, revid1)
798
expected_result = [revid1], [
799
(('', '', root_id), [d_entry, parent_entry])]
800
state = dirstate.DirState.initialize('dirstate')
802
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
803
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
805
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
806
state._dirblock_state)
811
# This will unlock it
812
self.check_state_with_reopen(expected_result, state)
814
def test_set_state_from_inventory_preserves_hashcache(self):
815
# https://bugs.launchpad.net/bzr/+bug/146176
816
# set_state_from_inventory should preserve the stat and hash value for
817
# workingtree files that are not changed by the inventory.
819
tree = self.make_branch_and_tree('.')
820
# depends on the default format using dirstate...
823
# make a dirstate with some valid hashcache data
824
# file on disk, but that's not needed for this test
825
foo_contents = 'contents of foo'
826
self.build_tree_contents([('foo', foo_contents)])
827
tree.add('foo', 'foo-id')
829
foo_stat = os.stat('foo')
830
foo_packed = dirstate.pack_stat(foo_stat)
831
foo_sha = osutils.sha_string(foo_contents)
832
foo_size = len(foo_contents)
834
# should not be cached yet, because the file's too fresh
836
(('', 'foo', 'foo-id',),
837
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
838
tree._dirstate._get_entry(0, 'foo-id'))
839
# poke in some hashcache information - it wouldn't normally be
840
# stored because it's too fresh
841
tree._dirstate.update_minimal(
842
('', 'foo', 'foo-id'),
843
'f', False, foo_sha, foo_packed, foo_size, 'foo')
844
# now should be cached
846
(('', 'foo', 'foo-id',),
847
[('f', foo_sha, foo_size, False, foo_packed)]),
848
tree._dirstate._get_entry(0, 'foo-id'))
850
# extract the inventory, and add something to it
851
inv = tree._get_inventory()
852
# should see the file we poked in...
853
self.assertTrue(inv.has_id('foo-id'))
854
self.assertTrue(inv.has_filename('foo'))
855
inv.add_path('bar', 'file', 'bar-id')
856
tree._dirstate._validate()
857
# this used to cause it to lose its hashcache
858
tree._dirstate.set_state_from_inventory(inv)
859
tree._dirstate._validate()
865
# now check that the state still has the original hashcache value
866
state = tree._dirstate
868
foo_tuple = state._get_entry(0, path_utf8='foo')
870
(('', 'foo', 'foo-id',),
871
[('f', foo_sha, len(foo_contents), False,
872
dirstate.pack_stat(foo_stat))]),
877
def test_set_state_from_inventory_mixed_paths(self):
878
tree1 = self.make_branch_and_tree('tree1')
879
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
880
'tree1/a/b/foo', 'tree1/a-b/bar'])
883
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
884
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
885
tree1.commit('rev1', rev_id='rev1')
886
root_id = tree1.get_root_id()
887
inv = tree1.inventory
890
expected_result1 = [('', '', root_id, 'd'),
891
('', 'a', 'a-id', 'd'),
892
('', 'a-b', 'a-b-id', 'd'),
893
('a', 'b', 'b-id', 'd'),
894
('a/b', 'foo', 'foo-id', 'f'),
895
('a-b', 'bar', 'bar-id', 'f'),
897
expected_result2 = [('', '', root_id, 'd'),
898
('', 'a', 'a-id', 'd'),
899
('', 'a-b', 'a-b-id', 'd'),
900
('a-b', 'bar', 'bar-id', 'f'),
902
state = dirstate.DirState.initialize('dirstate')
904
state.set_state_from_inventory(inv)
906
for entry in state._iter_entries():
907
values.append(entry[0] + entry[1][0][:1])
908
self.assertEqual(expected_result1, values)
910
state.set_state_from_inventory(inv)
912
for entry in state._iter_entries():
913
values.append(entry[0] + entry[1][0][:1])
914
self.assertEqual(expected_result2, values)
918
430
def test_set_path_id_no_parents(self):
919
431
"""The id of a path can be changed trivally with no parents."""
920
432
state = dirstate.DirState.initialize('dirstate')
922
434
# check precondition to be sure the state does change appropriately.
923
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
924
self.assertEqual([root_entry], list(state._iter_entries()))
925
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
926
self.assertEqual(root_entry,
927
state._get_entry(0, fileid_utf8='TREE_ROOT'))
928
self.assertEqual((None, None),
929
state._get_entry(0, fileid_utf8='second-root-id'))
930
state.set_path_id('', 'second-root-id')
931
new_root_entry = (('', '', 'second-root-id'),
932
[('d', '', 0, False, 'x'*32)])
933
expected_rows = [new_root_entry]
436
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
437
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
438
list(state._iter_entries()))
439
state.set_path_id('', 'foobarbaz')
441
(('', '', 'foobarbaz'), [('d', '', 0, False,
442
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
934
443
self.assertEqual(expected_rows, list(state._iter_entries()))
935
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
936
self.assertEqual(new_root_entry,
937
state._get_entry(0, fileid_utf8='second-root-id'))
938
self.assertEqual((None, None),
939
state._get_entry(0, fileid_utf8='TREE_ROOT'))
940
444
# should work across save too
944
448
state = dirstate.DirState.on_file('dirstate')
945
450
state.lock_read()
948
452
self.assertEqual(expected_rows, list(state._iter_entries()))
1559
class TestIterChildEntries(TestCaseWithDirState):
1561
def create_dirstate_with_two_trees(self):
1562
"""This dirstate contains multiple files and directories.
1572
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1574
Notice that a/e is an empty directory.
1576
There is one parent tree, which has the same shape with the following variations:
1577
b/g in the parent is gone.
1578
b/h in the parent has a different id
1579
b/i is new in the parent
1580
c is renamed to b/j in the parent
1582
:return: The dirstate, still write-locked.
1584
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1585
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1586
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1587
root_entry = ('', '', 'a-root-value'), [
1588
('d', '', 0, False, packed_stat),
1589
('d', '', 0, False, 'parent-revid'),
1591
a_entry = ('', 'a', 'a-dir'), [
1592
('d', '', 0, False, packed_stat),
1593
('d', '', 0, False, 'parent-revid'),
1595
b_entry = ('', 'b', 'b-dir'), [
1596
('d', '', 0, False, packed_stat),
1597
('d', '', 0, False, 'parent-revid'),
1599
c_entry = ('', 'c', 'c-file'), [
1600
('f', null_sha, 10, False, packed_stat),
1601
('r', 'b/j', 0, False, ''),
1603
d_entry = ('', 'd', 'd-file'), [
1604
('f', null_sha, 20, False, packed_stat),
1605
('f', 'd', 20, False, 'parent-revid'),
1607
e_entry = ('a', 'e', 'e-dir'), [
1608
('d', '', 0, False, packed_stat),
1609
('d', '', 0, False, 'parent-revid'),
1611
f_entry = ('a', 'f', 'f-file'), [
1612
('f', null_sha, 30, False, packed_stat),
1613
('f', 'f', 20, False, 'parent-revid'),
1615
g_entry = ('b', 'g', 'g-file'), [
1616
('f', null_sha, 30, False, packed_stat),
1617
NULL_PARENT_DETAILS,
1619
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1620
('f', null_sha, 40, False, packed_stat),
1621
NULL_PARENT_DETAILS,
1623
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1624
NULL_PARENT_DETAILS,
1625
('f', 'h', 20, False, 'parent-revid'),
1627
i_entry = ('b', 'i', 'i-file'), [
1628
NULL_PARENT_DETAILS,
1629
('f', 'h', 20, False, 'parent-revid'),
1631
j_entry = ('b', 'j', 'c-file'), [
1632
('r', 'c', 0, False, ''),
1633
('f', 'j', 20, False, 'parent-revid'),
1636
dirblocks.append(('', [root_entry]))
1637
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1638
dirblocks.append(('a', [e_entry, f_entry]))
1639
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1640
state = dirstate.DirState.initialize('dirstate')
1643
state._set_data(['parent'], dirblocks)
1647
return state, dirblocks
1649
def test_iter_children_b(self):
1650
state, dirblocks = self.create_dirstate_with_two_trees()
1651
self.addCleanup(state.unlock)
1652
expected_result = []
1653
expected_result.append(dirblocks[3][1][2]) # h2
1654
expected_result.append(dirblocks[3][1][3]) # i
1655
expected_result.append(dirblocks[3][1][4]) # j
1656
self.assertEqual(expected_result,
1657
list(state._iter_child_entries(1, 'b')))
1659
def test_iter_child_root(self):
1660
state, dirblocks = self.create_dirstate_with_two_trees()
1661
self.addCleanup(state.unlock)
1662
expected_result = []
1663
expected_result.append(dirblocks[1][1][0]) # a
1664
expected_result.append(dirblocks[1][1][1]) # b
1665
expected_result.append(dirblocks[1][1][3]) # d
1666
expected_result.append(dirblocks[2][1][0]) # e
1667
expected_result.append(dirblocks[2][1][1]) # f
1668
expected_result.append(dirblocks[3][1][2]) # h2
1669
expected_result.append(dirblocks[3][1][3]) # i
1670
expected_result.append(dirblocks[3][1][4]) # j
1671
self.assertEqual(expected_result,
1672
list(state._iter_child_entries(1, '')))
1675
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1017
class TestDirstateSortOrder(TestCaseWithTransport):
1676
1018
"""Test that DirState adds entries in the right order."""
1678
1020
def test_add_sorting(self):
1788
1127
self.st_ino = ino
1789
1128
self.st_mode = mode
1793
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1794
st.st_ino, st.st_mode)
1797
class TestPackStat(tests.TestCaseWithTransport):
1131
class TestUpdateEntry(TestCaseWithDirState):
1132
"""Test the DirState.update_entry functions"""
1134
def get_state_with_a(self):
1135
"""Create a DirState tracking a single object named 'a'"""
1136
state = InstrumentedDirState.initialize('dirstate')
1137
self.addCleanup(state.unlock)
1138
state.add('a', 'a-id', 'file', None, '')
1139
entry = state._get_entry(0, path_utf8='a')
1142
def test_update_entry(self):
1143
state, entry = self.get_state_with_a()
1144
self.build_tree(['a'])
1145
# Add one where we don't provide the stat or sha already
1146
self.assertEqual(('', 'a', 'a-id'), entry[0])
1147
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1149
# Flush the buffers to disk
1151
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1152
state._dirblock_state)
1154
stat_value = os.lstat('a')
1155
packed_stat = dirstate.pack_stat(stat_value)
1156
link_or_sha1 = state.update_entry(entry, abspath='a',
1157
stat_value=stat_value)
1158
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1161
# The dirblock entry should be updated with the new info
1162
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1164
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1165
state._dirblock_state)
1166
mode = stat_value.st_mode
1167
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1170
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1171
state._dirblock_state)
1173
# If we do it again right away, we don't know if the file has changed
1174
# so we will re-read the file. Roll the clock back so the file is
1175
# guaranteed to look too new.
1176
state.adjust_time(-10)
1178
link_or_sha1 = state.update_entry(entry, abspath='a',
1179
stat_value=stat_value)
1180
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1181
('sha1', 'a'), ('is_exec', mode, False),
1183
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1185
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1186
state._dirblock_state)
1189
# However, if we move the clock forward so the file is considered
1190
# "stable", it should just returned the cached value.
1191
state.adjust_time(20)
1192
link_or_sha1 = state.update_entry(entry, abspath='a',
1193
stat_value=stat_value)
1194
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1196
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1197
('sha1', 'a'), ('is_exec', mode, False),
1200
def test_update_entry_no_stat_value(self):
1201
"""Passing the stat_value is optional."""
1202
state, entry = self.get_state_with_a()
1203
state.adjust_time(-10) # Make sure the file looks new
1204
self.build_tree(['a'])
1205
# Add one where we don't provide the stat or sha already
1206
link_or_sha1 = state.update_entry(entry, abspath='a')
1207
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1209
stat_value = os.lstat('a')
1210
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1211
('is_exec', stat_value.st_mode, False),
1214
def test_update_entry_symlink(self):
1215
"""Update entry should read symlinks."""
1216
if not osutils.has_symlinks():
1217
return # PlatformDeficiency / TestSkipped
1218
state, entry = self.get_state_with_a()
1220
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1221
state._dirblock_state)
1222
os.symlink('target', 'a')
1224
state.adjust_time(-10) # Make the symlink look new
1225
stat_value = os.lstat('a')
1226
packed_stat = dirstate.pack_stat(stat_value)
1227
link_or_sha1 = state.update_entry(entry, abspath='a',
1228
stat_value=stat_value)
1229
self.assertEqual('target', link_or_sha1)
1230
self.assertEqual([('read_link', 'a', '')], state._log)
1231
# Dirblock is updated
1232
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1234
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1235
state._dirblock_state)
1237
# Because the stat_value looks new, we should re-read the target
1238
link_or_sha1 = state.update_entry(entry, abspath='a',
1239
stat_value=stat_value)
1240
self.assertEqual('target', link_or_sha1)
1241
self.assertEqual([('read_link', 'a', ''),
1242
('read_link', 'a', 'target'),
1244
state.adjust_time(+20) # Skip into the future, all files look old
1245
link_or_sha1 = state.update_entry(entry, abspath='a',
1246
stat_value=stat_value)
1247
self.assertEqual('target', link_or_sha1)
1248
# There should not be a new read_link call.
1249
# (this is a weak assertion, because read_link is fairly inexpensive,
1250
# versus the number of symlinks that we would have)
1251
self.assertEqual([('read_link', 'a', ''),
1252
('read_link', 'a', 'target'),
1255
def test_update_entry_dir(self):
1256
state, entry = self.get_state_with_a()
1257
self.build_tree(['a/'])
1258
self.assertIs(None, state.update_entry(entry, 'a'))
1260
def create_and_test_file(self, state, entry):
1261
"""Create a file at 'a' and verify the state finds it.
1263
The state should already be versioning *something* at 'a'. This makes
1264
sure that state.update_entry recognizes it as a file.
1266
self.build_tree(['a'])
1267
stat_value = os.lstat('a')
1268
packed_stat = dirstate.pack_stat(stat_value)
1270
link_or_sha1 = state.update_entry(entry, abspath='a')
1271
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1273
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1277
def create_and_test_dir(self, state, entry):
1278
"""Create a directory at 'a' and verify the state finds it.
1280
The state should already be versioning *something* at 'a'. This makes
1281
sure that state.update_entry recognizes it as a directory.
1283
self.build_tree(['a/'])
1284
stat_value = os.lstat('a')
1285
packed_stat = dirstate.pack_stat(stat_value)
1287
link_or_sha1 = state.update_entry(entry, abspath='a')
1288
self.assertIs(None, link_or_sha1)
1289
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1293
def create_and_test_symlink(self, state, entry):
1294
"""Create a symlink at 'a' and verify the state finds it.
1296
The state should already be versioning *something* at 'a'. This makes
1297
sure that state.update_entry recognizes it as a symlink.
1299
This should not be called if this platform does not have symlink
1302
os.symlink('path/to/foo', 'a')
1304
stat_value = os.lstat('a')
1305
packed_stat = dirstate.pack_stat(stat_value)
1307
link_or_sha1 = state.update_entry(entry, abspath='a')
1308
self.assertEqual('path/to/foo', link_or_sha1)
1309
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1313
def test_update_missing_file(self):
1314
state, entry = self.get_state_with_a()
1315
packed_stat = self.create_and_test_file(state, entry)
1316
# Now if we delete the file, update_entry should recover and
1319
self.assertIs(None, state.update_entry(entry, abspath='a'))
1320
# And the record shouldn't be changed.
1321
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1322
self.assertEqual([('f', digest, 14, False, packed_stat)],
1325
def test_update_missing_dir(self):
1326
state, entry = self.get_state_with_a()
1327
packed_stat = self.create_and_test_dir(state, entry)
1328
# Now if we delete the directory, update_entry should recover and
1331
self.assertIs(None, state.update_entry(entry, abspath='a'))
1332
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1334
def test_update_missing_symlink(self):
1335
if not osutils.has_symlinks():
1336
return # PlatformDeficiency / TestSkipped
1337
state, entry = self.get_state_with_a()
1338
packed_stat = self.create_and_test_symlink(state, entry)
1340
self.assertIs(None, state.update_entry(entry, abspath='a'))
1341
# And the record shouldn't be changed.
1342
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1345
def test_update_file_to_dir(self):
1346
"""If a file changes to a directory we return None for the sha.
1347
We also update the inventory record.
1349
state, entry = self.get_state_with_a()
1350
self.create_and_test_file(state, entry)
1352
self.create_and_test_dir(state, entry)
1354
def test_update_file_to_symlink(self):
1355
"""File becomes a symlink"""
1356
if not osutils.has_symlinks():
1357
return # PlatformDeficiency / TestSkipped
1358
state, entry = self.get_state_with_a()
1359
self.create_and_test_file(state, entry)
1361
self.create_and_test_symlink(state, entry)
1363
def test_update_dir_to_file(self):
1364
"""Directory becoming a file updates the entry."""
1365
state, entry = self.get_state_with_a()
1366
self.create_and_test_dir(state, entry)
1368
self.create_and_test_file(state, entry)
1370
def test_update_dir_to_symlink(self):
1371
"""Directory becomes a symlink"""
1372
if not osutils.has_symlinks():
1373
return # PlatformDeficiency / TestSkipped
1374
state, entry = self.get_state_with_a()
1375
self.create_and_test_dir(state, entry)
1377
self.create_and_test_symlink(state, entry)
1379
def test_update_symlink_to_file(self):
1380
"""Symlink becomes a file"""
1381
state, entry = self.get_state_with_a()
1382
self.create_and_test_symlink(state, entry)
1384
self.create_and_test_file(state, entry)
1386
def test_update_symlink_to_dir(self):
1387
"""Symlink becomes a directory"""
1388
state, entry = self.get_state_with_a()
1389
self.create_and_test_symlink(state, entry)
1391
self.create_and_test_dir(state, entry)
1393
def test__is_executable_win32(self):
1394
state, entry = self.get_state_with_a()
1395
self.build_tree(['a'])
1397
# Make sure we are using the win32 implementation of _is_executable
1398
state._is_executable = state._is_executable_win32
1400
# The file on disk is not executable, but we are marking it as though
1401
# it is. With _is_executable_win32 we ignore what is on disk.
1402
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1404
stat_value = os.lstat('a')
1405
packed_stat = dirstate.pack_stat(stat_value)
1407
state.adjust_time(-10) # Make sure everything is new
1408
# Make sure it wants to kkkkkkkk
1409
state.update_entry(entry, abspath='a', stat_value=stat_value)
1411
# The row is updated, but the executable bit stays set.
1412
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1413
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1416
class TestPackStat(TestCaseWithTransport):
1799
1418
def assertPackStat(self, expected, stat_value):
1800
1419
"""Check the packed and serialized form of a stat value."""
1846
1465
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1849
class TestBisect(TestCaseWithDirState):
1468
class TestBisect(TestCaseWithTransport):
1850
1469
"""Test the ability to bisect into the disk format."""
1471
def create_basic_dirstate(self):
1472
"""Create a dirstate with a few files and directories.
1481
tree = self.make_branch_and_tree('tree')
1482
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1483
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1484
self.build_tree(['tree/' + p for p in paths])
1485
tree.set_root_id('TREE_ROOT')
1486
tree.add([p.rstrip('/') for p in paths], file_ids)
1487
tree.commit('initial', rev_id='rev-1')
1488
revision_id = 'rev-1'
1489
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1490
t = self.get_transport().clone('tree')
1491
a_text = t.get_bytes('a')
1492
a_sha = osutils.sha_string(a_text)
1494
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1495
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1496
c_text = t.get_bytes('b/c')
1497
c_sha = osutils.sha_string(c_text)
1499
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1500
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1501
e_text = t.get_bytes('b/d/e')
1502
e_sha = osutils.sha_string(e_text)
1504
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1505
f_text = t.get_bytes('f')
1506
f_sha = osutils.sha_string(f_text)
1508
null_stat = dirstate.DirState.NULLSTAT
1510
'':(('', '', 'TREE_ROOT'), [
1511
('d', '', 0, False, null_stat),
1512
('d', '', 0, False, revision_id),
1514
'a':(('', 'a', 'a-id'), [
1515
('f', '', 0, False, null_stat),
1516
('f', a_sha, a_len, False, revision_id),
1518
'b':(('', 'b', 'b-id'), [
1519
('d', '', 0, False, null_stat),
1520
('d', '', 0, False, revision_id),
1522
'b/c':(('b', 'c', 'c-id'), [
1523
('f', '', 0, False, null_stat),
1524
('f', c_sha, c_len, False, revision_id),
1526
'b/d':(('b', 'd', 'd-id'), [
1527
('d', '', 0, False, null_stat),
1528
('d', '', 0, False, revision_id),
1530
'b/d/e':(('b/d', 'e', 'e-id'), [
1531
('f', '', 0, False, null_stat),
1532
('f', e_sha, e_len, False, revision_id),
1534
'f':(('', 'f', 'f-id'), [
1535
('f', '', 0, False, null_stat),
1536
('f', f_sha, f_len, False, revision_id),
1539
state = dirstate.DirState.from_tree(tree, 'dirstate')
1544
# Use a different object, to make sure nothing is pre-cached in memory.
1545
state = dirstate.DirState.on_file('dirstate')
1547
self.addCleanup(state.unlock)
1548
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1549
state._dirblock_state)
1550
# This is code is only really tested if we actually have to make more
1551
# than one read, so set the page size to something smaller.
1552
# We want it to contain about 2.2 records, so that we have a couple
1553
# records that we can read per attempt
1554
state._bisect_page_size = 200
1555
return tree, state, expected
1557
def create_duplicated_dirstate(self):
1558
"""Create a dirstate with a deleted and added entries.
1560
This grabs a basic_dirstate, and then removes and re adds every entry
1563
tree, state, expected = self.create_basic_dirstate()
1564
# Now we will just remove and add every file so we get an extra entry
1565
# per entry. Unversion in reverse order so we handle subdirs
1566
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1567
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1568
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1570
# Update the expected dictionary.
1571
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1572
orig = expected[path]
1574
# This record was deleted in the current tree
1575
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1577
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1578
# And didn't exist in the basis tree
1579
expected[path2] = (new_key, [orig[1][0],
1580
dirstate.DirState.NULL_PARENT_DETAILS])
1582
# We will replace the 'dirstate' file underneath 'state', but that is
1583
# okay as lock as we unlock 'state' first.
1586
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1592
# But we need to leave state in a read-lock because we already have
1593
# a cleanup scheduled
1595
return tree, state, expected
1597
def create_renamed_dirstate(self):
1598
"""Create a dirstate with a few internal renames.
1600
This takes the basic dirstate, and moves the paths around.
1602
tree, state, expected = self.create_basic_dirstate()
1604
tree.rename_one('a', 'b/g')
1606
tree.rename_one('b/d', 'h')
1608
old_a = expected['a']
1609
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1610
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1611
('r', 'a', 0, False, '')])
1612
old_d = expected['b/d']
1613
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1614
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1615
('r', 'b/d', 0, False, '')])
1617
old_e = expected['b/d/e']
1618
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1620
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1621
('r', 'b/d/e', 0, False, '')])
1625
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1632
return tree, state, expected
1852
1634
def assertBisect(self, expected_map, map_keys, state, paths):
1853
1635
"""Assert that bisecting for paths returns the right result.
2088
class TestDirstateValidation(TestCaseWithDirState):
2090
def test_validate_correct_dirstate(self):
2091
state = self.create_complex_dirstate()
2094
# and make sure we can also validate with a read lock
2101
def test_dirblock_not_sorted(self):
2102
tree, state, expected = self.create_renamed_dirstate()
2103
state._read_dirblocks_if_needed()
2104
last_dirblock = state._dirblocks[-1]
2105
# we're appending to the dirblock, but this name comes before some of
2106
# the existing names; that's wrong
2107
last_dirblock[1].append(
2108
(('h', 'aaaa', 'a-id'),
2109
[('a', '', 0, False, ''),
2110
('a', '', 0, False, '')]))
2111
e = self.assertRaises(AssertionError,
2113
self.assertContainsRe(str(e), 'not sorted')
2115
def test_dirblock_name_mismatch(self):
2116
tree, state, expected = self.create_renamed_dirstate()
2117
state._read_dirblocks_if_needed()
2118
last_dirblock = state._dirblocks[-1]
2119
# add an entry with the wrong directory name
2120
last_dirblock[1].append(
2122
[('a', '', 0, False, ''),
2123
('a', '', 0, False, '')]))
2124
e = self.assertRaises(AssertionError,
2126
self.assertContainsRe(str(e),
2127
"doesn't match directory name")
2129
def test_dirblock_missing_rename(self):
2130
tree, state, expected = self.create_renamed_dirstate()
2131
state._read_dirblocks_if_needed()
2132
last_dirblock = state._dirblocks[-1]
2133
# make another entry for a-id, without a correct 'r' pointer to
2134
# the real occurrence in the working tree
2135
last_dirblock[1].append(
2136
(('h', 'z', 'a-id'),
2137
[('a', '', 0, False, ''),
2138
('a', '', 0, False, '')]))
2139
e = self.assertRaises(AssertionError,
2141
self.assertContainsRe(str(e),
2142
'file a-id is absent in row')
2145
class TestDirstateTreeReference(TestCaseWithDirState):
2147
def test_reference_revision_is_none(self):
2148
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2149
subtree = self.make_branch_and_tree('tree/subtree',
2150
format='dirstate-with-subtree')
2151
subtree.set_root_id('subtree')
2152
tree.add_reference(subtree)
2154
state = dirstate.DirState.from_tree(tree, 'dirstate')
2155
key = ('', 'subtree', 'subtree')
2156
expected = ('', [(key,
2157
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2160
self.assertEqual(expected, state._find_block(key))
2165
class TestDiscardMergeParents(TestCaseWithDirState):
2167
def test_discard_no_parents(self):
2168
# This should be a no-op
2169
state = self.create_empty_dirstate()
2170
self.addCleanup(state.unlock)
2171
state._discard_merge_parents()
2174
def test_discard_one_parent(self):
2176
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2177
root_entry_direntry = ('', '', 'a-root-value'), [
2178
('d', '', 0, False, packed_stat),
2179
('d', '', 0, False, packed_stat),
2182
dirblocks.append(('', [root_entry_direntry]))
2183
dirblocks.append(('', []))
2185
state = self.create_empty_dirstate()
2186
self.addCleanup(state.unlock)
2187
state._set_data(['parent-id'], dirblocks[:])
2190
state._discard_merge_parents()
2192
self.assertEqual(dirblocks, state._dirblocks)
2194
def test_discard_simple(self):
2196
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2197
root_entry_direntry = ('', '', 'a-root-value'), [
2198
('d', '', 0, False, packed_stat),
2199
('d', '', 0, False, packed_stat),
2200
('d', '', 0, False, packed_stat),
2202
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2203
('d', '', 0, False, packed_stat),
2204
('d', '', 0, False, packed_stat),
2207
dirblocks.append(('', [root_entry_direntry]))
2208
dirblocks.append(('', []))
2210
state = self.create_empty_dirstate()
2211
self.addCleanup(state.unlock)
2212
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2215
# This should strip of the extra column
2216
state._discard_merge_parents()
2218
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2219
self.assertEqual(expected_dirblocks, state._dirblocks)
2221
def test_discard_absent(self):
2222
"""If entries are only in a merge, discard should remove the entries"""
2223
null_stat = dirstate.DirState.NULLSTAT
2224
present_dir = ('d', '', 0, False, null_stat)
2225
present_file = ('f', '', 0, False, null_stat)
2226
absent = dirstate.DirState.NULL_PARENT_DETAILS
2227
root_key = ('', '', 'a-root-value')
2228
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2229
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2230
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2231
('', [(file_in_merged_key,
2232
[absent, absent, present_file]),
2234
[present_file, present_file, present_file]),
2238
state = self.create_empty_dirstate()
2239
self.addCleanup(state.unlock)
2240
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2243
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2244
('', [(file_in_root_key,
2245
[present_file, present_file]),
2248
state._discard_merge_parents()
2250
self.assertEqual(exp_dirblocks, state._dirblocks)
2252
def test_discard_renamed(self):
2253
null_stat = dirstate.DirState.NULLSTAT
2254
present_dir = ('d', '', 0, False, null_stat)
2255
present_file = ('f', '', 0, False, null_stat)
2256
absent = dirstate.DirState.NULL_PARENT_DETAILS
2257
root_key = ('', '', 'a-root-value')
2258
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2259
# Renamed relative to parent
2260
file_rename_s_key = ('', 'file-s', 'b-file-id')
2261
file_rename_t_key = ('', 'file-t', 'b-file-id')
2262
# And one that is renamed between the parents, but absent in this
2263
key_in_1 = ('', 'file-in-1', 'c-file-id')
2264
key_in_2 = ('', 'file-in-2', 'c-file-id')
2267
('', [(root_key, [present_dir, present_dir, present_dir])]),
2269
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2271
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2273
[present_file, present_file, present_file]),
2275
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2277
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2281
('', [(root_key, [present_dir, present_dir])]),
2282
('', [(key_in_1, [absent, present_file]),
2283
(file_in_root_key, [present_file, present_file]),
2284
(file_rename_t_key, [present_file, absent]),
2287
state = self.create_empty_dirstate()
2288
self.addCleanup(state.unlock)
2289
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2292
state._discard_merge_parents()
2294
self.assertEqual(exp_dirblocks, state._dirblocks)
2296
def test_discard_all_subdir(self):
2297
null_stat = dirstate.DirState.NULLSTAT
2298
present_dir = ('d', '', 0, False, null_stat)
2299
present_file = ('f', '', 0, False, null_stat)
2300
absent = dirstate.DirState.NULL_PARENT_DETAILS
2301
root_key = ('', '', 'a-root-value')
2302
subdir_key = ('', 'sub', 'dir-id')
2303
child1_key = ('sub', 'child1', 'child1-id')
2304
child2_key = ('sub', 'child2', 'child2-id')
2305
child3_key = ('sub', 'child3', 'child3-id')
2308
('', [(root_key, [present_dir, present_dir, present_dir])]),
2309
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2310
('sub', [(child1_key, [absent, absent, present_file]),
2311
(child2_key, [absent, absent, present_file]),
2312
(child3_key, [absent, absent, present_file]),
2316
('', [(root_key, [present_dir, present_dir])]),
2317
('', [(subdir_key, [present_dir, present_dir])]),
2320
state = self.create_empty_dirstate()
2321
self.addCleanup(state.unlock)
2322
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2325
state._discard_merge_parents()
2327
self.assertEqual(exp_dirblocks, state._dirblocks)
2330
class Test_InvEntryToDetails(tests.TestCase):
2332
def assertDetails(self, expected, inv_entry):
2333
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2334
self.assertEqual(expected, details)
2335
# details should always allow join() and always be a plain str when
2337
(minikind, fingerprint, size, executable, tree_data) = details
2338
self.assertIsInstance(minikind, str)
2339
self.assertIsInstance(fingerprint, str)
2340
self.assertIsInstance(tree_data, str)
2342
def test_unicode_symlink(self):
2343
inv_entry = inventory.InventoryLink('link-file-id',
2344
u'nam\N{Euro Sign}e',
2346
inv_entry.revision = 'link-revision-id'
2347
target = u'link-targ\N{Euro Sign}t'
2348
inv_entry.symlink_target = target
2349
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2350
'link-revision-id'), inv_entry)
2353
class TestSHA1Provider(tests.TestCaseInTempDir):
2355
def test_sha1provider_is_an_interface(self):
2356
p = dirstate.SHA1Provider()
2357
self.assertRaises(NotImplementedError, p.sha1, "foo")
2358
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2360
def test_defaultsha1provider_sha1(self):
2361
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2362
self.build_tree_contents([('foo', text)])
2363
expected_sha = osutils.sha_string(text)
2364
p = dirstate.DefaultSHA1Provider()
2365
self.assertEqual(expected_sha, p.sha1('foo'))
2367
def test_defaultsha1provider_stat_and_sha1(self):
2368
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2369
self.build_tree_contents([('foo', text)])
2370
expected_sha = osutils.sha_string(text)
2371
p = dirstate.DefaultSHA1Provider()
2372
statvalue, sha1 = p.stat_and_sha1('foo')
2373
self.assertTrue(len(statvalue) >= 10)
2374
self.assertEqual(len(text), statvalue.st_size)
2375
self.assertEqual(expected_sha, sha1)
1862
class TestBisectDirblock(TestCase):
1863
"""Test that bisect_dirblock() returns the expected values.
1865
bisect_dirblock is intended to work like bisect.bisect_left() except it
1866
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1867
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1870
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1871
"""Assert that bisect_split works like bisect_left on the split paths.
1873
:param dirblocks: A list of (path, [info]) pairs.
1874
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1875
:param path: The path we are indexing.
1877
All other arguments will be passed along.
1879
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1881
split_dirblock = (path.split('/'), [])
1882
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1884
self.assertEqual(bisect_left_idx, bisect_split_idx,
1885
'bisect_split disagreed. %s != %s'
1887
% (bisect_left_idx, bisect_split_idx, path)
1890
def paths_to_dirblocks(self, paths):
1891
"""Convert a list of paths into dirblock form.
1893
Also, ensure that the paths are in proper sorted order.
1895
dirblocks = [(path, []) for path in paths]
1896
split_dirblocks = [(path.split('/'), []) for path in paths]
1897
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1898
return dirblocks, split_dirblocks
1900
def test_simple(self):
1901
"""In the simple case it works just like bisect_left"""
1902
paths = ['', 'a', 'b', 'c', 'd']
1903
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1905
self.assertBisect(dirblocks, split_dirblocks, path)
1906
self.assertBisect(dirblocks, split_dirblocks, '_')
1907
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1908
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1909
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1910
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1911
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1912
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1913
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1914
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1916
def test_involved(self):
1917
"""This is where bisect_left diverges slightly."""
1919
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1920
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1922
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1923
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1926
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1928
self.assertBisect(dirblocks, split_dirblocks, path)
1930
def test_involved_cached(self):
1931
"""This is where bisect_left diverges slightly."""
1933
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1934
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1936
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1937
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1941
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1943
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)