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
178
class TestTreeToDirState(TestCaseWithDirState):
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 sha1 sum should be empty
567
self.assertEqual('', entry[1][0][1])
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
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
574
# We should have gotten a real sha1
575
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
578
# The dirblock has been updated
579
self.assertEqual(sha1sum, entry[1][0][1])
580
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
581
state._dirblock_state)
584
# Now, since we are the only one holding a lock, we should be able
585
# to save and have it written to disk
590
# Re-open the file, and ensure that the state has been updated.
591
state = dirstate.DirState.on_file('dirstate')
594
entry = state._get_entry(0, path_utf8='a-file')
595
self.assertEqual(sha1sum, entry[1][0][1])
599
def test_save_fails_quietly_if_locked(self):
600
"""If dirstate is locked, save will fail without complaining."""
601
self.build_tree(['a-file'])
602
state = dirstate.DirState.initialize('dirstate')
604
# No stat and no sha1 sum.
605
state.add('a-file', 'a-file-id', 'file', None, '')
610
state = dirstate.DirState.on_file('dirstate')
613
entry = state._get_entry(0, path_utf8='a-file')
614
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
615
# We should have gotten a real sha1
616
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
618
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
619
state._dirblock_state)
621
# Now, before we try to save, grab another dirstate, and take out a
623
# TODO: jam 20070315 Ideally this would be locked by another
624
# process. To make sure the file is really OS locked.
625
state2 = dirstate.DirState.on_file('dirstate')
628
# This won't actually write anything, because it couldn't grab
629
# a write lock. But it shouldn't raise an error, either.
630
# TODO: jam 20070315 We should probably distinguish between
631
# being dirty because of 'update_entry'. And dirty
632
# because of real modification. So that save() *does*
633
# raise a real error if it fails when we have real
641
# The file on disk should not be modified.
642
state = dirstate.DirState.on_file('dirstate')
645
entry = state._get_entry(0, path_utf8='a-file')
646
self.assertEqual('', entry[1][0][1])
651
366
class TestDirStateInitialize(TestCaseWithDirState):
700
413
# This will unlock it
701
414
self.check_state_with_reopen(expected_result, state)
703
def test_set_state_from_inventory_mixed_paths(self):
704
tree1 = self.make_branch_and_tree('tree1')
705
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
706
'tree1/a/b/foo', 'tree1/a-b/bar'])
709
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
710
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
711
tree1.commit('rev1', rev_id='rev1')
712
root_id = tree1.get_root_id()
713
inv = tree1.inventory
716
expected_result1 = [('', '', root_id, 'd'),
717
('', 'a', 'a-id', 'd'),
718
('', 'a-b', 'a-b-id', 'd'),
719
('a', 'b', 'b-id', 'd'),
720
('a/b', 'foo', 'foo-id', 'f'),
721
('a-b', 'bar', 'bar-id', 'f'),
723
expected_result2 = [('', '', root_id, 'd'),
724
('', 'a', 'a-id', 'd'),
725
('', 'a-b', 'a-b-id', 'd'),
726
('a-b', 'bar', 'bar-id', 'f'),
728
state = dirstate.DirState.initialize('dirstate')
730
state.set_state_from_inventory(inv)
732
for entry in state._iter_entries():
733
values.append(entry[0] + entry[1][0][:1])
734
self.assertEqual(expected_result1, values)
736
state.set_state_from_inventory(inv)
738
for entry in state._iter_entries():
739
values.append(entry[0] + entry[1][0][:1])
740
self.assertEqual(expected_result2, values)
744
416
def test_set_path_id_no_parents(self):
745
417
"""The id of a path can be changed trivally with no parents."""
746
418
state = dirstate.DirState.initialize('dirstate')
1501
1172
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1502
1173
state._dirblock_state)
1503
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1507
1176
# However, if we move the clock forward so the file is considered
1508
# "stable", it should just cache the value.
1509
state.adjust_time(+20)
1177
# "stable", it should just returned the cached value.
1178
state.adjust_time(20)
1510
1179
link_or_sha1 = state.update_entry(entry, abspath='a',
1511
1180
stat_value=stat_value)
1512
1181
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1514
1183
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1515
1184
('sha1', 'a'), ('is_exec', mode, False),
1516
('sha1', 'a'), ('is_exec', mode, False),
1518
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1521
# Subsequent calls will just return the cached value
1522
link_or_sha1 = state.update_entry(entry, abspath='a',
1523
stat_value=stat_value)
1187
def test_update_entry_no_stat_value(self):
1188
"""Passing the stat_value is optional."""
1189
state, entry = self.get_state_with_a()
1190
state.adjust_time(-10) # Make sure the file looks new
1191
self.build_tree(['a'])
1192
# Add one where we don't provide the stat or sha already
1193
link_or_sha1 = state.update_entry(entry, abspath='a')
1524
1194
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1526
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1527
('sha1', 'a'), ('is_exec', mode, False),
1528
('sha1', 'a'), ('is_exec', mode, False),
1196
stat_value = os.lstat('a')
1197
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1198
('is_exec', stat_value.st_mode, False),
1530
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1533
1201
def test_update_entry_symlink(self):
1534
1202
"""Update entry should read symlinks."""
1535
1203
if not osutils.has_symlinks():
1536
# PlatformDeficiency / TestSkipped
1537
raise TestSkipped("No symlink support")
1204
return # PlatformDeficiency / TestSkipped
1538
1205
state, entry = self.get_state_with_a()
1540
1207
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1559
1226
stat_value=stat_value)
1560
1227
self.assertEqual('target', link_or_sha1)
1561
1228
self.assertEqual([('read_link', 'a', ''),
1562
('read_link', 'a', ''),
1229
('read_link', 'a', 'target'),
1564
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1566
1231
state.adjust_time(+20) # Skip into the future, all files look old
1567
1232
link_or_sha1 = state.update_entry(entry, abspath='a',
1568
1233
stat_value=stat_value)
1569
1234
self.assertEqual('target', link_or_sha1)
1570
# We need to re-read the link because only now can we cache it
1571
self.assertEqual([('read_link', 'a', ''),
1572
('read_link', 'a', ''),
1573
('read_link', 'a', ''),
1575
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1578
# Another call won't re-read the link
1579
self.assertEqual([('read_link', 'a', ''),
1580
('read_link', 'a', ''),
1581
('read_link', 'a', ''),
1583
link_or_sha1 = state.update_entry(entry, abspath='a',
1584
stat_value=stat_value)
1585
self.assertEqual('target', link_or_sha1)
1586
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1589
def do_update_entry(self, state, entry, abspath):
1590
stat_value = os.lstat(abspath)
1591
return state.update_entry(entry, abspath, stat_value)
1235
# There should not be a new read_link call.
1236
# (this is a weak assertion, because read_link is fairly inexpensive,
1237
# versus the number of symlinks that we would have)
1238
self.assertEqual([('read_link', 'a', ''),
1239
('read_link', 'a', 'target'),
1593
1242
def test_update_entry_dir(self):
1594
1243
state, entry = self.get_state_with_a()
1595
1244
self.build_tree(['a/'])
1596
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1598
def test_update_entry_dir_unchanged(self):
1599
state, entry = self.get_state_with_a()
1600
self.build_tree(['a/'])
1601
state.adjust_time(+20)
1602
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1603
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1604
state._dirblock_state)
1606
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1607
state._dirblock_state)
1608
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1609
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1610
state._dirblock_state)
1612
def test_update_entry_file_unchanged(self):
1613
state, entry = self.get_state_with_a()
1614
self.build_tree(['a'])
1615
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1616
state.adjust_time(+20)
1617
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1618
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1619
state._dirblock_state)
1621
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1622
state._dirblock_state)
1623
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1624
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1625
state._dirblock_state)
1245
self.assertIs(None, state.update_entry(entry, 'a'))
1627
1247
def create_and_test_file(self, state, entry):
1628
1248
"""Create a file at 'a' and verify the state finds it.
1666
1286
This should not be called if this platform does not have symlink
1669
# caller should care about skipping test on platforms without symlinks
1670
1289
os.symlink('path/to/foo', 'a')
1672
1291
stat_value = os.lstat('a')
1673
1292
packed_stat = dirstate.pack_stat(stat_value)
1675
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1294
link_or_sha1 = state.update_entry(entry, abspath='a')
1676
1295
self.assertEqual('path/to/foo', link_or_sha1)
1677
1296
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1679
1298
return packed_stat
1300
def test_update_missing_file(self):
1301
state, entry = self.get_state_with_a()
1302
packed_stat = self.create_and_test_file(state, entry)
1303
# Now if we delete the file, update_entry should recover and
1306
self.assertIs(None, state.update_entry(entry, abspath='a'))
1307
# And the record shouldn't be changed.
1308
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1309
self.assertEqual([('f', digest, 14, False, packed_stat)],
1312
def test_update_missing_dir(self):
1313
state, entry = self.get_state_with_a()
1314
packed_stat = self.create_and_test_dir(state, entry)
1315
# Now if we delete the directory, update_entry should recover and
1318
self.assertIs(None, state.update_entry(entry, abspath='a'))
1319
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1321
def test_update_missing_symlink(self):
1322
if not osutils.has_symlinks():
1323
return # PlatformDeficiency / TestSkipped
1324
state, entry = self.get_state_with_a()
1325
packed_stat = self.create_and_test_symlink(state, entry)
1327
self.assertIs(None, state.update_entry(entry, abspath='a'))
1328
# And the record shouldn't be changed.
1329
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1681
1332
def test_update_file_to_dir(self):
1682
1333
"""If a file changes to a directory we return None for the sha.
1683
1334
We also update the inventory record.
1685
1336
state, entry = self.get_state_with_a()
1686
# The file sha1 won't be cached unless the file is old
1687
state.adjust_time(+10)
1688
1337
self.create_and_test_file(state, entry)
1690
1339
self.create_and_test_dir(state, entry)
1824
1452
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1827
class TestBisect(TestCaseWithDirState):
1455
class TestBisect(TestCaseWithTransport):
1828
1456
"""Test the ability to bisect into the disk format."""
1458
def create_basic_dirstate(self):
1459
"""Create a dirstate with a few files and directories.
1468
tree = self.make_branch_and_tree('tree')
1469
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1470
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1471
self.build_tree(['tree/' + p for p in paths])
1472
tree.set_root_id('TREE_ROOT')
1473
tree.add([p.rstrip('/') for p in paths], file_ids)
1474
tree.commit('initial', rev_id='rev-1')
1475
revision_id = 'rev-1'
1476
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1477
t = self.get_transport().clone('tree')
1478
a_text = t.get_bytes('a')
1479
a_sha = osutils.sha_string(a_text)
1481
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1482
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1483
c_text = t.get_bytes('b/c')
1484
c_sha = osutils.sha_string(c_text)
1486
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1487
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1488
e_text = t.get_bytes('b/d/e')
1489
e_sha = osutils.sha_string(e_text)
1491
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1492
f_text = t.get_bytes('f')
1493
f_sha = osutils.sha_string(f_text)
1495
null_stat = dirstate.DirState.NULLSTAT
1497
'':(('', '', 'TREE_ROOT'), [
1498
('d', '', 0, False, null_stat),
1499
('d', '', 0, False, revision_id),
1501
'a':(('', 'a', 'a-id'), [
1502
('f', '', 0, False, null_stat),
1503
('f', a_sha, a_len, False, revision_id),
1505
'b':(('', 'b', 'b-id'), [
1506
('d', '', 0, False, null_stat),
1507
('d', '', 0, False, revision_id),
1509
'b/c':(('b', 'c', 'c-id'), [
1510
('f', '', 0, False, null_stat),
1511
('f', c_sha, c_len, False, revision_id),
1513
'b/d':(('b', 'd', 'd-id'), [
1514
('d', '', 0, False, null_stat),
1515
('d', '', 0, False, revision_id),
1517
'b/d/e':(('b/d', 'e', 'e-id'), [
1518
('f', '', 0, False, null_stat),
1519
('f', e_sha, e_len, False, revision_id),
1521
'f':(('', 'f', 'f-id'), [
1522
('f', '', 0, False, null_stat),
1523
('f', f_sha, f_len, False, revision_id),
1526
state = dirstate.DirState.from_tree(tree, 'dirstate')
1531
# Use a different object, to make sure nothing is pre-cached in memory.
1532
state = dirstate.DirState.on_file('dirstate')
1534
self.addCleanup(state.unlock)
1535
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1536
state._dirblock_state)
1537
# This is code is only really tested if we actually have to make more
1538
# than one read, so set the page size to something smaller.
1539
# We want it to contain about 2.2 records, so that we have a couple
1540
# records that we can read per attempt
1541
state._bisect_page_size = 200
1542
return tree, state, expected
1544
def create_duplicated_dirstate(self):
1545
"""Create a dirstate with a deleted and added entries.
1547
This grabs a basic_dirstate, and then removes and re adds every entry
1550
tree, state, expected = self.create_basic_dirstate()
1551
# Now we will just remove and add every file so we get an extra entry
1552
# per entry. Unversion in reverse order so we handle subdirs
1553
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1554
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1555
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1557
# Update the expected dictionary.
1558
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1559
orig = expected[path]
1561
# This record was deleted in the current tree
1562
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1564
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1565
# And didn't exist in the basis tree
1566
expected[path2] = (new_key, [orig[1][0],
1567
dirstate.DirState.NULL_PARENT_DETAILS])
1569
# We will replace the 'dirstate' file underneath 'state', but that is
1570
# okay as lock as we unlock 'state' first.
1573
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1579
# But we need to leave state in a read-lock because we already have
1580
# a cleanup scheduled
1582
return tree, state, expected
1584
def create_renamed_dirstate(self):
1585
"""Create a dirstate with a few internal renames.
1587
This takes the basic dirstate, and moves the paths around.
1589
tree, state, expected = self.create_basic_dirstate()
1591
tree.rename_one('a', 'b/g')
1593
tree.rename_one('b/d', 'h')
1595
old_a = expected['a']
1596
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1597
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1598
('r', 'a', 0, False, '')])
1599
old_d = expected['b/d']
1600
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1601
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1602
('r', 'b/d', 0, False, '')])
1604
old_e = expected['b/d/e']
1605
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1607
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1608
('r', 'b/d/e', 0, False, '')])
1612
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1619
return tree, state, expected
1830
1621
def assertBisect(self, expected_map, map_keys, state, paths):
1831
1622
"""Assert that bisecting for paths returns the right result.
2001
1785
def test_bisect_dirblocks(self):
2002
1786
tree, state, expected = self.create_duplicated_dirstate()
2003
1787
self.assertBisectDirBlocks(expected,
2004
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1788
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
2006
1789
self.assertBisectDirBlocks(expected,
2007
1790
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2008
1791
self.assertBisectDirBlocks(expected,
2009
1792
[['b/d/e', 'b/d/e2']], state, ['b/d'])
2010
1793
self.assertBisectDirBlocks(expected,
2011
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1794
[['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
2012
1795
['b/c', 'b/c2', 'b/d', 'b/d2'],
2013
1796
['b/d/e', 'b/d/e2'],
2014
1797
], state, ['', 'b', 'b/d'])
2067
class TestDirstateValidation(TestCaseWithDirState):
2069
def test_validate_correct_dirstate(self):
2070
state = self.create_complex_dirstate()
2073
# and make sure we can also validate with a read lock
2080
def test_dirblock_not_sorted(self):
2081
tree, state, expected = self.create_renamed_dirstate()
2082
state._read_dirblocks_if_needed()
2083
last_dirblock = state._dirblocks[-1]
2084
# we're appending to the dirblock, but this name comes before some of
2085
# the existing names; that's wrong
2086
last_dirblock[1].append(
2087
(('h', 'aaaa', 'a-id'),
2088
[('a', '', 0, False, ''),
2089
('a', '', 0, False, '')]))
2090
e = self.assertRaises(AssertionError,
2092
self.assertContainsRe(str(e), 'not sorted')
2094
def test_dirblock_name_mismatch(self):
2095
tree, state, expected = self.create_renamed_dirstate()
2096
state._read_dirblocks_if_needed()
2097
last_dirblock = state._dirblocks[-1]
2098
# add an entry with the wrong directory name
2099
last_dirblock[1].append(
2101
[('a', '', 0, False, ''),
2102
('a', '', 0, False, '')]))
2103
e = self.assertRaises(AssertionError,
2105
self.assertContainsRe(str(e),
2106
"doesn't match directory name")
2108
def test_dirblock_missing_rename(self):
2109
tree, state, expected = self.create_renamed_dirstate()
2110
state._read_dirblocks_if_needed()
2111
last_dirblock = state._dirblocks[-1]
2112
# make another entry for a-id, without a correct 'r' pointer to
2113
# the real occurrence in the working tree
2114
last_dirblock[1].append(
2115
(('h', 'z', 'a-id'),
2116
[('a', '', 0, False, ''),
2117
('a', '', 0, False, '')]))
2118
e = self.assertRaises(AssertionError,
2120
self.assertContainsRe(str(e),
2121
'file a-id is absent in row')
1849
class TestBisectDirblock(TestCase):
1850
"""Test that bisect_dirblock() returns the expected values.
1852
bisect_dirblock is intended to work like bisect.bisect_left() except it
1853
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1854
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1857
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1858
"""Assert that bisect_split works like bisect_left on the split paths.
1860
:param dirblocks: A list of (path, [info]) pairs.
1861
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1862
:param path: The path we are indexing.
1864
All other arguments will be passed along.
1866
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1868
split_dirblock = (path.split('/'), [])
1869
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1871
self.assertEqual(bisect_left_idx, bisect_split_idx,
1872
'bisect_split disagreed. %s != %s'
1874
% (bisect_left_idx, bisect_split_idx, path)
1877
def paths_to_dirblocks(self, paths):
1878
"""Convert a list of paths into dirblock form.
1880
Also, ensure that the paths are in proper sorted order.
1882
dirblocks = [(path, []) for path in paths]
1883
split_dirblocks = [(path.split('/'), []) for path in paths]
1884
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1885
return dirblocks, split_dirblocks
1887
def test_simple(self):
1888
"""In the simple case it works just like bisect_left"""
1889
paths = ['', 'a', 'b', 'c', 'd']
1890
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1892
self.assertBisect(dirblocks, split_dirblocks, path)
1893
self.assertBisect(dirblocks, split_dirblocks, '_')
1894
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1895
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1896
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1897
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1898
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1899
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1900
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1901
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1903
def test_involved(self):
1904
"""This is where bisect_left diverges slightly."""
1906
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1907
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1909
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1910
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1913
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1915
self.assertBisect(dirblocks, split_dirblocks, path)
1917
def test_involved_cached(self):
1918
"""This is where bisect_left diverges slightly."""
1920
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1921
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1923
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1924
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1928
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1930
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)