184
def create_basic_dirstate(self):
185
"""Create a dirstate with a few files and directories.
194
tree = self.make_branch_and_tree('tree')
195
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
196
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
197
self.build_tree(['tree/' + p for p in paths])
198
tree.set_root_id('TREE_ROOT')
199
tree.add([p.rstrip('/') for p in paths], file_ids)
200
tree.commit('initial', rev_id='rev-1')
201
revision_id = 'rev-1'
202
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
203
t = self.get_transport().clone('tree')
204
a_text = t.get_bytes('a')
205
a_sha = osutils.sha_string(a_text)
207
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
208
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
209
c_text = t.get_bytes('b/c')
210
c_sha = osutils.sha_string(c_text)
212
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
213
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
214
e_text = t.get_bytes('b/d/e')
215
e_sha = osutils.sha_string(e_text)
217
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
218
f_text = t.get_bytes('f')
219
f_sha = osutils.sha_string(f_text)
221
null_stat = dirstate.DirState.NULLSTAT
223
'':(('', '', 'TREE_ROOT'), [
224
('d', '', 0, False, null_stat),
225
('d', '', 0, False, revision_id),
227
'a':(('', 'a', 'a-id'), [
228
('f', '', 0, False, null_stat),
229
('f', a_sha, a_len, False, revision_id),
231
'b':(('', 'b', 'b-id'), [
232
('d', '', 0, False, null_stat),
233
('d', '', 0, False, revision_id),
235
'b/c':(('b', 'c', 'c-id'), [
236
('f', '', 0, False, null_stat),
237
('f', c_sha, c_len, False, revision_id),
239
'b/d':(('b', 'd', 'd-id'), [
240
('d', '', 0, False, null_stat),
241
('d', '', 0, False, revision_id),
243
'b/d/e':(('b/d', 'e', 'e-id'), [
244
('f', '', 0, False, null_stat),
245
('f', e_sha, e_len, False, revision_id),
247
'f':(('', 'f', 'f-id'), [
248
('f', '', 0, False, null_stat),
249
('f', f_sha, f_len, False, revision_id),
252
state = dirstate.DirState.from_tree(tree, 'dirstate')
257
# Use a different object, to make sure nothing is pre-cached in memory.
258
state = dirstate.DirState.on_file('dirstate')
260
self.addCleanup(state.unlock)
261
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
262
state._dirblock_state)
263
# This is code is only really tested if we actually have to make more
264
# than one read, so set the page size to something smaller.
265
# We want it to contain about 2.2 records, so that we have a couple
266
# records that we can read per attempt
267
state._bisect_page_size = 200
268
return tree, state, expected
270
def create_duplicated_dirstate(self):
271
"""Create a dirstate with a deleted and added entries.
273
This grabs a basic_dirstate, and then removes and re adds every entry
276
tree, state, expected = self.create_basic_dirstate()
277
# Now we will just remove and add every file so we get an extra entry
278
# per entry. Unversion in reverse order so we handle subdirs
279
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
280
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
281
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
283
# Update the expected dictionary.
284
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
285
orig = expected[path]
287
# This record was deleted in the current tree
288
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
290
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
291
# And didn't exist in the basis tree
292
expected[path2] = (new_key, [orig[1][0],
293
dirstate.DirState.NULL_PARENT_DETAILS])
295
# We will replace the 'dirstate' file underneath 'state', but that is
296
# okay as lock as we unlock 'state' first.
299
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
305
# But we need to leave state in a read-lock because we already have
306
# a cleanup scheduled
308
return tree, state, expected
310
def create_renamed_dirstate(self):
311
"""Create a dirstate with a few internal renames.
313
This takes the basic dirstate, and moves the paths around.
315
tree, state, expected = self.create_basic_dirstate()
317
tree.rename_one('a', 'b/g')
319
tree.rename_one('b/d', 'h')
321
old_a = expected['a']
322
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
323
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
324
('r', 'a', 0, False, '')])
325
old_d = expected['b/d']
326
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
327
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
328
('r', 'b/d', 0, False, '')])
330
old_e = expected['b/d/e']
331
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
333
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
334
('r', 'b/d/e', 0, False, '')])
338
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
345
return tree, state, expected
178
347
class TestTreeToDirState(TestCaseWithDirState):
542
def test_can_save_in_read_lock(self):
543
self.build_tree(['a-file'])
544
state = dirstate.DirState.initialize('dirstate')
546
# No stat and no sha1 sum.
547
state.add('a-file', 'a-file-id', 'file', None, '')
552
# Now open in readonly mode
553
state = dirstate.DirState.on_file('dirstate')
556
entry = state._get_entry(0, path_utf8='a-file')
557
# The current sha1 sum should be empty
558
self.assertEqual('', entry[1][0][1])
559
# We should have a real entry.
560
self.assertNotEqual((None, None), entry)
561
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
562
# We should have gotten a real sha1
563
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
566
# The dirblock has been updated
567
self.assertEqual(sha1sum, entry[1][0][1])
568
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
569
state._dirblock_state)
572
# Now, since we are the only one holding a lock, we should be able
573
# to save and have it written to disk
578
# Re-open the file, and ensure that the state has been updated.
579
state = dirstate.DirState.on_file('dirstate')
582
entry = state._get_entry(0, path_utf8='a-file')
583
self.assertEqual(sha1sum, entry[1][0][1])
587
def test_save_fails_quietly_if_locked(self):
588
"""If dirstate is locked, save will fail without complaining."""
589
self.build_tree(['a-file'])
590
state = dirstate.DirState.initialize('dirstate')
592
# No stat and no sha1 sum.
593
state.add('a-file', 'a-file-id', 'file', None, '')
598
state = dirstate.DirState.on_file('dirstate')
601
entry = state._get_entry(0, path_utf8='a-file')
602
sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
603
# We should have gotten a real sha1
604
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
606
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
607
state._dirblock_state)
609
# Now, before we try to save, grab another dirstate, and take out a
611
# TODO: jam 20070315 Ideally this would be locked by another
612
# process. To make sure the file is really OS locked.
613
state2 = dirstate.DirState.on_file('dirstate')
616
# This won't actually write anything, because it couldn't grab
617
# a write lock. But it shouldn't raise an error, either.
618
# TODO: jam 20070315 We should probably distinguish between
619
# being dirty because of 'update_entry'. And dirty
620
# because of real modification. So that save() *does*
621
# raise a real error if it fails when we have real
629
# The file on disk should not be modified.
630
state = dirstate.DirState.on_file('dirstate')
633
entry = state._get_entry(0, path_utf8='a-file')
634
self.assertEqual('', entry[1][0][1])
366
639
class TestDirStateInitialize(TestCaseWithDirState):
1452
1737
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1455
class TestBisect(TestCaseWithTransport):
1740
class TestBisect(TestCaseWithDirState):
1456
1741
"""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
1621
1744
def assertBisect(self, expected_map, map_keys, state, paths):
1622
1745
"""Assert that bisecting for paths returns the right result.
1929
2052
for path in paths:
1930
2053
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
2056
class TestDirstateValidation(TestCaseWithDirState):
2058
def test_validate_correct_dirstate(self):
2059
state = self.create_complex_dirstate()
2062
# and make sure we can also validate with a read lock
2069
def test_dirblock_not_sorted(self):
2070
tree, state, expected = self.create_renamed_dirstate()
2071
state._read_dirblocks_if_needed()
2072
last_dirblock = state._dirblocks[-1]
2073
# we're appending to the dirblock, but this name comes before some of
2074
# the existing names; that's wrong
2075
last_dirblock[1].append(
2076
(('h', 'aaaa', 'a-id'),
2077
[('a', '', 0, False, ''),
2078
('a', '', 0, False, '')]))
2079
e = self.assertRaises(AssertionError,
2081
self.assertContainsRe(str(e), 'not sorted')
2083
def test_dirblock_name_mismatch(self):
2084
tree, state, expected = self.create_renamed_dirstate()
2085
state._read_dirblocks_if_needed()
2086
last_dirblock = state._dirblocks[-1]
2087
# add an entry with the wrong directory name
2088
last_dirblock[1].append(
2090
[('a', '', 0, False, ''),
2091
('a', '', 0, False, '')]))
2092
e = self.assertRaises(AssertionError,
2094
self.assertContainsRe(str(e),
2095
"doesn't match directory name")
2097
def test_dirblock_missing_rename(self):
2098
tree, state, expected = self.create_renamed_dirstate()
2099
state._read_dirblocks_if_needed()
2100
last_dirblock = state._dirblocks[-1]
2101
# make another entry for a-id, without a correct 'r' pointer to
2102
# the real occurrence in the working tree
2103
last_dirblock[1].append(
2104
(('h', 'z', 'a-id'),
2105
[('a', '', 0, False, ''),
2106
('a', '', 0, False, '')]))
2107
e = self.assertRaises(AssertionError,
2109
self.assertContainsRe(str(e),
2110
'file a-id is absent in row')