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
183
347
class TestTreeToDirState(TestCaseWithDirState):
1563
1735
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1566
class TestBisect(TestCaseWithTransport):
1738
class TestBisect(TestCaseWithDirState):
1567
1739
"""Test the ability to bisect into the disk format."""
1569
def create_basic_dirstate(self):
1570
"""Create a dirstate with a few files and directories.
1579
tree = self.make_branch_and_tree('tree')
1580
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1581
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1582
self.build_tree(['tree/' + p for p in paths])
1583
tree.set_root_id('TREE_ROOT')
1584
tree.add([p.rstrip('/') for p in paths], file_ids)
1585
tree.commit('initial', rev_id='rev-1')
1586
revision_id = 'rev-1'
1587
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1588
t = self.get_transport().clone('tree')
1589
a_text = t.get_bytes('a')
1590
a_sha = osutils.sha_string(a_text)
1592
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1593
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1594
c_text = t.get_bytes('b/c')
1595
c_sha = osutils.sha_string(c_text)
1597
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1598
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1599
e_text = t.get_bytes('b/d/e')
1600
e_sha = osutils.sha_string(e_text)
1602
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1603
f_text = t.get_bytes('f')
1604
f_sha = osutils.sha_string(f_text)
1606
null_stat = dirstate.DirState.NULLSTAT
1608
'':(('', '', 'TREE_ROOT'), [
1609
('d', '', 0, False, null_stat),
1610
('d', '', 0, False, revision_id),
1612
'a':(('', 'a', 'a-id'), [
1613
('f', '', 0, False, null_stat),
1614
('f', a_sha, a_len, False, revision_id),
1616
'b':(('', 'b', 'b-id'), [
1617
('d', '', 0, False, null_stat),
1618
('d', '', 0, False, revision_id),
1620
'b/c':(('b', 'c', 'c-id'), [
1621
('f', '', 0, False, null_stat),
1622
('f', c_sha, c_len, False, revision_id),
1624
'b/d':(('b', 'd', 'd-id'), [
1625
('d', '', 0, False, null_stat),
1626
('d', '', 0, False, revision_id),
1628
'b/d/e':(('b/d', 'e', 'e-id'), [
1629
('f', '', 0, False, null_stat),
1630
('f', e_sha, e_len, False, revision_id),
1632
'f':(('', 'f', 'f-id'), [
1633
('f', '', 0, False, null_stat),
1634
('f', f_sha, f_len, False, revision_id),
1637
state = dirstate.DirState.from_tree(tree, 'dirstate')
1642
# Use a different object, to make sure nothing is pre-cached in memory.
1643
state = dirstate.DirState.on_file('dirstate')
1645
self.addCleanup(state.unlock)
1646
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1647
state._dirblock_state)
1648
# This is code is only really tested if we actually have to make more
1649
# than one read, so set the page size to something smaller.
1650
# We want it to contain about 2.2 records, so that we have a couple
1651
# records that we can read per attempt
1652
state._bisect_page_size = 200
1653
return tree, state, expected
1655
def create_duplicated_dirstate(self):
1656
"""Create a dirstate with a deleted and added entries.
1658
This grabs a basic_dirstate, and then removes and re adds every entry
1661
tree, state, expected = self.create_basic_dirstate()
1662
# Now we will just remove and add every file so we get an extra entry
1663
# per entry. Unversion in reverse order so we handle subdirs
1664
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1665
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1666
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1668
# Update the expected dictionary.
1669
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1670
orig = expected[path]
1672
# This record was deleted in the current tree
1673
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1675
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1676
# And didn't exist in the basis tree
1677
expected[path2] = (new_key, [orig[1][0],
1678
dirstate.DirState.NULL_PARENT_DETAILS])
1680
# We will replace the 'dirstate' file underneath 'state', but that is
1681
# okay as lock as we unlock 'state' first.
1684
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1690
# But we need to leave state in a read-lock because we already have
1691
# a cleanup scheduled
1693
return tree, state, expected
1695
def create_renamed_dirstate(self):
1696
"""Create a dirstate with a few internal renames.
1698
This takes the basic dirstate, and moves the paths around.
1700
tree, state, expected = self.create_basic_dirstate()
1702
tree.rename_one('a', 'b/g')
1704
tree.rename_one('b/d', 'h')
1706
old_a = expected['a']
1707
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1708
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1709
('r', 'a', 0, False, '')])
1710
old_d = expected['b/d']
1711
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1712
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1713
('r', 'b/d', 0, False, '')])
1715
old_e = expected['b/d/e']
1716
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1718
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1719
('r', 'b/d/e', 0, False, '')])
1723
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1730
return tree, state, expected
1732
1742
def assertBisect(self, expected_map, map_keys, state, paths):
1733
1743
"""Assert that bisecting for paths returns the right result.
2040
2050
for path in paths:
2041
2051
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
2054
class TestDirstateValidation(TestCaseWithDirState):
2056
def test_validate_correct_dirstate(self):
2057
state = self.create_complex_dirstate()
2060
# and make sure we can also validate with a read lock
2067
def test_dirblock_not_sorted(self):
2068
tree, state, expected = self.create_renamed_dirstate()
2069
state._read_dirblocks_if_needed()
2070
last_dirblock = state._dirblocks[-1]
2071
# we're appending to the dirblock, but this name comes before some of
2072
# the existing names; that's wrong
2073
last_dirblock[1].append(
2074
(('h', 'aaaa', 'a-id'),
2075
[('a', '', 0, False, ''),
2076
('a', '', 0, False, '')]))
2077
e = self.assertRaises(AssertionError,
2079
self.assertContainsRe(str(e), 'not sorted')
2081
def test_dirblock_name_mismatch(self):
2082
tree, state, expected = self.create_renamed_dirstate()
2083
state._read_dirblocks_if_needed()
2084
last_dirblock = state._dirblocks[-1]
2085
# add an entry with the wrong directory name
2086
last_dirblock[1].append(
2088
[('a', '', 0, False, ''),
2089
('a', '', 0, False, '')]))
2090
e = self.assertRaises(AssertionError,
2092
self.assertContainsRe(str(e),
2093
"doesn't match directory name")
2095
def test_dirblock_missing_rename(self):
2096
tree, state, expected = self.create_renamed_dirstate()
2097
state._read_dirblocks_if_needed()
2098
last_dirblock = state._dirblocks[-1]
2099
# make another entry for a-id, without a correct 'r' pointer to
2100
# the real occurrence in the working tree
2101
last_dirblock[1].append(
2102
(('h', 'z', 'a-id'),
2103
[('a', '', 0, False, ''),
2104
('a', '', 0, False, '')]))
2105
e = self.assertRaises(AssertionError,
2107
self.assertContainsRe(str(e),
2108
'file a-id is absent in row')