202
def create_basic_dirstate(self):
203
"""Create a dirstate with a few files and directories.
213
tree = self.make_branch_and_tree('tree')
214
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
215
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
216
self.build_tree(['tree/' + p for p in paths])
217
tree.set_root_id('TREE_ROOT')
218
tree.add([p.rstrip('/') for p in paths], file_ids)
219
tree.commit('initial', rev_id='rev-1')
220
revision_id = 'rev-1'
221
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
222
t = self.get_transport('tree')
223
a_text = t.get_bytes('a')
224
a_sha = osutils.sha_string(a_text)
226
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
227
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
228
c_text = t.get_bytes('b/c')
229
c_sha = osutils.sha_string(c_text)
231
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
232
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
233
e_text = t.get_bytes('b/d/e')
234
e_sha = osutils.sha_string(e_text)
236
b_c_text = t.get_bytes('b-c')
237
b_c_sha = osutils.sha_string(b_c_text)
238
b_c_len = len(b_c_text)
239
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
240
f_text = t.get_bytes('f')
241
f_sha = osutils.sha_string(f_text)
243
null_stat = dirstate.DirState.NULLSTAT
245
'':(('', '', 'TREE_ROOT'), [
246
('d', '', 0, False, null_stat),
247
('d', '', 0, False, revision_id),
249
'a':(('', 'a', 'a-id'), [
250
('f', '', 0, False, null_stat),
251
('f', a_sha, a_len, False, revision_id),
253
'b':(('', 'b', 'b-id'), [
254
('d', '', 0, False, null_stat),
255
('d', '', 0, False, revision_id),
257
'b/c':(('b', 'c', 'c-id'), [
258
('f', '', 0, False, null_stat),
259
('f', c_sha, c_len, False, revision_id),
261
'b/d':(('b', 'd', 'd-id'), [
262
('d', '', 0, False, null_stat),
263
('d', '', 0, False, revision_id),
265
'b/d/e':(('b/d', 'e', 'e-id'), [
266
('f', '', 0, False, null_stat),
267
('f', e_sha, e_len, False, revision_id),
269
'b-c':(('', 'b-c', 'b-c-id'), [
270
('f', '', 0, False, null_stat),
271
('f', b_c_sha, b_c_len, False, revision_id),
273
'f':(('', 'f', 'f-id'), [
274
('f', '', 0, False, null_stat),
275
('f', f_sha, f_len, False, revision_id),
278
state = dirstate.DirState.from_tree(tree, 'dirstate')
283
# Use a different object, to make sure nothing is pre-cached in memory.
284
state = dirstate.DirState.on_file('dirstate')
286
self.addCleanup(state.unlock)
287
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
288
state._dirblock_state)
289
# This is code is only really tested if we actually have to make more
290
# than one read, so set the page size to something smaller.
291
# We want it to contain about 2.2 records, so that we have a couple
292
# records that we can read per attempt
293
state._bisect_page_size = 200
294
return tree, state, expected
296
def create_duplicated_dirstate(self):
297
"""Create a dirstate with a deleted and added entries.
299
This grabs a basic_dirstate, and then removes and re adds every entry
302
tree, state, expected = self.create_basic_dirstate()
303
# Now we will just remove and add every file so we get an extra entry
304
# per entry. Unversion in reverse order so we handle subdirs
305
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
306
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
307
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
309
# Update the expected dictionary.
310
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
311
orig = expected[path]
313
# This record was deleted in the current tree
314
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
316
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
317
# And didn't exist in the basis tree
318
expected[path2] = (new_key, [orig[1][0],
319
dirstate.DirState.NULL_PARENT_DETAILS])
321
# We will replace the 'dirstate' file underneath 'state', but that is
322
# okay as lock as we unlock 'state' first.
325
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
331
# But we need to leave state in a read-lock because we already have
332
# a cleanup scheduled
334
return tree, state, expected
336
def create_renamed_dirstate(self):
337
"""Create a dirstate with a few internal renames.
339
This takes the basic dirstate, and moves the paths around.
341
tree, state, expected = self.create_basic_dirstate()
343
tree.rename_one('a', 'b/g')
345
tree.rename_one('b/d', 'h')
347
old_a = expected['a']
348
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
349
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
350
('r', 'a', 0, False, '')])
351
old_d = expected['b/d']
352
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
353
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
354
('r', 'b/d', 0, False, '')])
356
old_e = expected['b/d/e']
357
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
359
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
360
('r', 'b/d/e', 0, False, '')])
364
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
371
return tree, state, expected
178
374
class TestTreeToDirState(TestCaseWithDirState):
413
773
# This will unlock it
414
774
self.check_state_with_reopen(expected_result, state)
776
def test_set_state_from_scratch_no_parents(self):
777
tree1, revid1 = self.make_minimal_tree()
778
inv = tree1.root_inventory
779
root_id = inv.path2id('')
780
expected_result = [], [
781
(('', '', root_id), [
782
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
783
state = dirstate.DirState.initialize('dirstate')
785
state.set_state_from_scratch(inv, [], [])
786
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
788
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
789
state._dirblock_state)
794
# This will unlock it
795
self.check_state_with_reopen(expected_result, state)
797
def test_set_state_from_scratch_identical_parent(self):
798
tree1, revid1 = self.make_minimal_tree()
799
inv = tree1.root_inventory
800
root_id = inv.path2id('')
801
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
802
d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
803
parent_entry = ('d', '', 0, False, revid1)
804
expected_result = [revid1], [
805
(('', '', root_id), [d_entry, parent_entry])]
806
state = dirstate.DirState.initialize('dirstate')
808
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
809
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
811
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
812
state._dirblock_state)
817
# This will unlock it
818
self.check_state_with_reopen(expected_result, state)
820
def test_set_state_from_inventory_preserves_hashcache(self):
821
# https://bugs.launchpad.net/bzr/+bug/146176
822
# set_state_from_inventory should preserve the stat and hash value for
823
# workingtree files that are not changed by the inventory.
825
tree = self.make_branch_and_tree('.')
826
# depends on the default format using dirstate...
829
# make a dirstate with some valid hashcache data
830
# file on disk, but that's not needed for this test
831
foo_contents = 'contents of foo'
832
self.build_tree_contents([('foo', foo_contents)])
833
tree.add('foo', 'foo-id')
835
foo_stat = os.stat('foo')
836
foo_packed = dirstate.pack_stat(foo_stat)
837
foo_sha = osutils.sha_string(foo_contents)
838
foo_size = len(foo_contents)
840
# should not be cached yet, because the file's too fresh
842
(('', 'foo', 'foo-id',),
843
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
844
tree._dirstate._get_entry(0, 'foo-id'))
845
# poke in some hashcache information - it wouldn't normally be
846
# stored because it's too fresh
847
tree._dirstate.update_minimal(
848
('', 'foo', 'foo-id'),
849
'f', False, foo_sha, foo_packed, foo_size, 'foo')
850
# now should be cached
852
(('', 'foo', 'foo-id',),
853
[('f', foo_sha, foo_size, False, foo_packed)]),
854
tree._dirstate._get_entry(0, 'foo-id'))
856
# extract the inventory, and add something to it
857
inv = tree._get_root_inventory()
858
# should see the file we poked in...
859
self.assertTrue(inv.has_id('foo-id'))
860
self.assertTrue(inv.has_filename('foo'))
861
inv.add_path('bar', 'file', 'bar-id')
862
tree._dirstate._validate()
863
# this used to cause it to lose its hashcache
864
tree._dirstate.set_state_from_inventory(inv)
865
tree._dirstate._validate()
871
# now check that the state still has the original hashcache value
872
state = tree._dirstate
874
foo_tuple = state._get_entry(0, path_utf8='foo')
876
(('', 'foo', 'foo-id',),
877
[('f', foo_sha, len(foo_contents), False,
878
dirstate.pack_stat(foo_stat))]),
883
def test_set_state_from_inventory_mixed_paths(self):
884
tree1 = self.make_branch_and_tree('tree1')
885
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
886
'tree1/a/b/foo', 'tree1/a-b/bar'])
889
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
890
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
891
tree1.commit('rev1', rev_id='rev1')
892
root_id = tree1.get_root_id()
893
inv = tree1.root_inventory
896
expected_result1 = [('', '', root_id, 'd'),
897
('', 'a', 'a-id', 'd'),
898
('', 'a-b', 'a-b-id', 'd'),
899
('a', 'b', 'b-id', 'd'),
900
('a/b', 'foo', 'foo-id', 'f'),
901
('a-b', 'bar', 'bar-id', 'f'),
903
expected_result2 = [('', '', root_id, 'd'),
904
('', 'a', 'a-id', 'd'),
905
('', 'a-b', 'a-b-id', 'd'),
906
('a-b', 'bar', 'bar-id', 'f'),
908
state = dirstate.DirState.initialize('dirstate')
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_result1, values)
916
state.set_state_from_inventory(inv)
918
for entry in state._iter_entries():
919
values.append(entry[0] + entry[1][0][:1])
920
self.assertEqual(expected_result2, values)
416
924
def test_set_path_id_no_parents(self):
417
925
"""The id of a path can be changed trivally with no parents."""
418
926
state = dirstate.DirState.initialize('dirstate')
420
928
# check precondition to be sure the state does change appropriately.
422
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
423
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
424
list(state._iter_entries()))
425
state.set_path_id('', 'foobarbaz')
427
(('', '', 'foobarbaz'), [('d', '', 0, False,
428
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
929
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
930
self.assertEqual([root_entry], list(state._iter_entries()))
931
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
932
self.assertEqual(root_entry,
933
state._get_entry(0, fileid_utf8='TREE_ROOT'))
934
self.assertEqual((None, None),
935
state._get_entry(0, fileid_utf8='second-root-id'))
936
state.set_path_id('', 'second-root-id')
937
new_root_entry = (('', '', 'second-root-id'),
938
[('d', '', 0, False, 'x'*32)])
939
expected_rows = [new_root_entry]
429
940
self.assertEqual(expected_rows, list(state._iter_entries()))
941
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
942
self.assertEqual(new_root_entry,
943
state._get_entry(0, fileid_utf8='second-root-id'))
944
self.assertEqual((None, None),
945
state._get_entry(0, fileid_utf8='TREE_ROOT'))
430
946
# should work across save too
434
950
state = dirstate.DirState.on_file('dirstate')
436
951
state.lock_read()
438
954
self.assertEqual(expected_rows, list(state._iter_entries()))
1114
1851
self.st_ino = ino
1115
1852
self.st_mode = mode
1118
class TestUpdateEntry(TestCaseWithDirState):
1119
"""Test the DirState.update_entry functions"""
1121
def get_state_with_a(self):
1122
"""Create a DirState tracking a single object named 'a'"""
1123
state = InstrumentedDirState.initialize('dirstate')
1124
self.addCleanup(state.unlock)
1125
state.add('a', 'a-id', 'file', None, '')
1126
entry = state._get_entry(0, path_utf8='a')
1129
def test_update_entry(self):
1130
state, entry = self.get_state_with_a()
1131
self.build_tree(['a'])
1132
# Add one where we don't provide the stat or sha already
1133
self.assertEqual(('', 'a', 'a-id'), entry[0])
1134
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1136
# Flush the buffers to disk
1138
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1139
state._dirblock_state)
1141
stat_value = os.lstat('a')
1142
packed_stat = dirstate.pack_stat(stat_value)
1143
link_or_sha1 = state.update_entry(entry, abspath='a',
1144
stat_value=stat_value)
1145
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1148
# The dirblock entry should be updated with the new info
1149
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1151
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1152
state._dirblock_state)
1153
mode = stat_value.st_mode
1154
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1157
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1158
state._dirblock_state)
1160
# If we do it again right away, we don't know if the file has changed
1161
# so we will re-read the file. Roll the clock back so the file is
1162
# guaranteed to look too new.
1163
state.adjust_time(-10)
1165
link_or_sha1 = state.update_entry(entry, abspath='a',
1166
stat_value=stat_value)
1167
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1168
('sha1', 'a'), ('is_exec', mode, False),
1170
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1172
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1173
state._dirblock_state)
1176
# However, if we move the clock forward so the file is considered
1177
# "stable", it should just returned the cached value.
1178
state.adjust_time(20)
1179
link_or_sha1 = state.update_entry(entry, abspath='a',
1180
stat_value=stat_value)
1181
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1183
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1184
('sha1', 'a'), ('is_exec', mode, False),
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')
1194
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1196
stat_value = os.lstat('a')
1197
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1198
('is_exec', stat_value.st_mode, False),
1201
def test_update_entry_symlink(self):
1202
"""Update entry should read symlinks."""
1203
if not osutils.has_symlinks():
1204
return # PlatformDeficiency / TestSkipped
1205
state, entry = self.get_state_with_a()
1207
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1208
state._dirblock_state)
1209
os.symlink('target', 'a')
1211
state.adjust_time(-10) # Make the symlink look new
1212
stat_value = os.lstat('a')
1213
packed_stat = dirstate.pack_stat(stat_value)
1214
link_or_sha1 = state.update_entry(entry, abspath='a',
1215
stat_value=stat_value)
1216
self.assertEqual('target', link_or_sha1)
1217
self.assertEqual([('read_link', 'a', '')], state._log)
1218
# Dirblock is updated
1219
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1221
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1222
state._dirblock_state)
1224
# Because the stat_value looks new, we should re-read the target
1225
link_or_sha1 = state.update_entry(entry, abspath='a',
1226
stat_value=stat_value)
1227
self.assertEqual('target', link_or_sha1)
1228
self.assertEqual([('read_link', 'a', ''),
1229
('read_link', 'a', 'target'),
1231
state.adjust_time(+20) # Skip into the future, all files look old
1232
link_or_sha1 = state.update_entry(entry, abspath='a',
1233
stat_value=stat_value)
1234
self.assertEqual('target', link_or_sha1)
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'),
1242
def test_update_entry_dir(self):
1243
state, entry = self.get_state_with_a()
1244
self.build_tree(['a/'])
1245
self.assertIs(None, state.update_entry(entry, 'a'))
1247
def create_and_test_file(self, state, entry):
1248
"""Create a file at 'a' and verify the state finds it.
1250
The state should already be versioning *something* at 'a'. This makes
1251
sure that state.update_entry recognizes it as a file.
1253
self.build_tree(['a'])
1254
stat_value = os.lstat('a')
1255
packed_stat = dirstate.pack_stat(stat_value)
1257
link_or_sha1 = state.update_entry(entry, abspath='a')
1258
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1260
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1264
def create_and_test_dir(self, state, entry):
1265
"""Create a directory at 'a' and verify the state finds it.
1267
The state should already be versioning *something* at 'a'. This makes
1268
sure that state.update_entry recognizes it as a directory.
1270
self.build_tree(['a/'])
1271
stat_value = os.lstat('a')
1272
packed_stat = dirstate.pack_stat(stat_value)
1274
link_or_sha1 = state.update_entry(entry, abspath='a')
1275
self.assertIs(None, link_or_sha1)
1276
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1280
def create_and_test_symlink(self, state, entry):
1281
"""Create a symlink at 'a' and verify the state finds it.
1283
The state should already be versioning *something* at 'a'. This makes
1284
sure that state.update_entry recognizes it as a symlink.
1286
This should not be called if this platform does not have symlink
1289
os.symlink('path/to/foo', 'a')
1291
stat_value = os.lstat('a')
1292
packed_stat = dirstate.pack_stat(stat_value)
1294
link_or_sha1 = state.update_entry(entry, abspath='a')
1295
self.assertEqual('path/to/foo', link_or_sha1)
1296
self.assertEqual([('l', 'path/to/foo', 11, False, 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)],
1332
def test_update_file_to_dir(self):
1333
"""If a file changes to a directory we return None for the sha.
1334
We also update the inventory record.
1336
state, entry = self.get_state_with_a()
1337
self.create_and_test_file(state, entry)
1339
self.create_and_test_dir(state, entry)
1341
def test_update_file_to_symlink(self):
1342
"""File becomes a symlink"""
1343
if not osutils.has_symlinks():
1344
return # PlatformDeficiency / TestSkipped
1345
state, entry = self.get_state_with_a()
1346
self.create_and_test_file(state, entry)
1348
self.create_and_test_symlink(state, entry)
1350
def test_update_dir_to_file(self):
1351
"""Directory becoming a file updates the entry."""
1352
state, entry = self.get_state_with_a()
1353
self.create_and_test_dir(state, entry)
1355
self.create_and_test_file(state, entry)
1357
def test_update_dir_to_symlink(self):
1358
"""Directory becomes a symlink"""
1359
if not osutils.has_symlinks():
1360
return # PlatformDeficiency / TestSkipped
1361
state, entry = self.get_state_with_a()
1362
self.create_and_test_dir(state, entry)
1364
self.create_and_test_symlink(state, entry)
1366
def test_update_symlink_to_file(self):
1367
"""Symlink becomes a file"""
1368
state, entry = self.get_state_with_a()
1369
self.create_and_test_symlink(state, entry)
1371
self.create_and_test_file(state, entry)
1373
def test_update_symlink_to_dir(self):
1374
"""Symlink becomes a directory"""
1375
state, entry = self.get_state_with_a()
1376
self.create_and_test_symlink(state, entry)
1378
self.create_and_test_dir(state, entry)
1380
def test__is_executable_win32(self):
1381
state, entry = self.get_state_with_a()
1382
self.build_tree(['a'])
1384
# Make sure we are using the win32 implementation of _is_executable
1385
state._is_executable = state._is_executable_win32
1387
# The file on disk is not executable, but we are marking it as though
1388
# it is. With _is_executable_win32 we ignore what is on disk.
1389
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1391
stat_value = os.lstat('a')
1392
packed_stat = dirstate.pack_stat(stat_value)
1394
state.adjust_time(-10) # Make sure everything is new
1395
# Make sure it wants to kkkkkkkk
1396
state.update_entry(entry, abspath='a', stat_value=stat_value)
1398
# The row is updated, but the executable bit stays set.
1399
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1400
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1403
class TestPackStat(TestCaseWithTransport):
1856
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1857
st.st_ino, st.st_mode)
1860
class TestPackStat(tests.TestCaseWithTransport):
1405
1862
def assertPackStat(self, expected, stat_value):
1406
1863
"""Check the packed and serialized form of a stat value."""
1452
1909
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1455
class TestBisect(TestCaseWithTransport):
1912
class TestBisect(TestCaseWithDirState):
1456
1913
"""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
1915
def assertBisect(self, expected_map, map_keys, state, paths):
1622
1916
"""Assert that bisecting for paths returns the right result.
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)
2151
class TestDirstateValidation(TestCaseWithDirState):
2153
def test_validate_correct_dirstate(self):
2154
state = self.create_complex_dirstate()
2157
# and make sure we can also validate with a read lock
2164
def test_dirblock_not_sorted(self):
2165
tree, state, expected = self.create_renamed_dirstate()
2166
state._read_dirblocks_if_needed()
2167
last_dirblock = state._dirblocks[-1]
2168
# we're appending to the dirblock, but this name comes before some of
2169
# the existing names; that's wrong
2170
last_dirblock[1].append(
2171
(('h', 'aaaa', 'a-id'),
2172
[('a', '', 0, False, ''),
2173
('a', '', 0, False, '')]))
2174
e = self.assertRaises(AssertionError,
2176
self.assertContainsRe(str(e), 'not sorted')
2178
def test_dirblock_name_mismatch(self):
2179
tree, state, expected = self.create_renamed_dirstate()
2180
state._read_dirblocks_if_needed()
2181
last_dirblock = state._dirblocks[-1]
2182
# add an entry with the wrong directory name
2183
last_dirblock[1].append(
2185
[('a', '', 0, False, ''),
2186
('a', '', 0, False, '')]))
2187
e = self.assertRaises(AssertionError,
2189
self.assertContainsRe(str(e),
2190
"doesn't match directory name")
2192
def test_dirblock_missing_rename(self):
2193
tree, state, expected = self.create_renamed_dirstate()
2194
state._read_dirblocks_if_needed()
2195
last_dirblock = state._dirblocks[-1]
2196
# make another entry for a-id, without a correct 'r' pointer to
2197
# the real occurrence in the working tree
2198
last_dirblock[1].append(
2199
(('h', 'z', 'a-id'),
2200
[('a', '', 0, False, ''),
2201
('a', '', 0, False, '')]))
2202
e = self.assertRaises(AssertionError,
2204
self.assertContainsRe(str(e),
2205
'file a-id is absent in row')
2208
class TestDirstateTreeReference(TestCaseWithDirState):
2210
def test_reference_revision_is_none(self):
2211
tree = self.make_branch_and_tree('tree', format='development-subtree')
2212
subtree = self.make_branch_and_tree('tree/subtree',
2213
format='development-subtree')
2214
subtree.set_root_id('subtree')
2215
tree.add_reference(subtree)
2217
state = dirstate.DirState.from_tree(tree, 'dirstate')
2218
key = ('', 'subtree', 'subtree')
2219
expected = ('', [(key,
2220
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2223
self.assertEqual(expected, state._find_block(key))
2228
class TestDiscardMergeParents(TestCaseWithDirState):
2230
def test_discard_no_parents(self):
2231
# This should be a no-op
2232
state = self.create_empty_dirstate()
2233
self.addCleanup(state.unlock)
2234
state._discard_merge_parents()
2237
def test_discard_one_parent(self):
2239
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2240
root_entry_direntry = ('', '', 'a-root-value'), [
2241
('d', '', 0, False, packed_stat),
2242
('d', '', 0, False, packed_stat),
2245
dirblocks.append(('', [root_entry_direntry]))
2246
dirblocks.append(('', []))
2248
state = self.create_empty_dirstate()
2249
self.addCleanup(state.unlock)
2250
state._set_data(['parent-id'], dirblocks[:])
2253
state._discard_merge_parents()
2255
self.assertEqual(dirblocks, state._dirblocks)
2257
def test_discard_simple(self):
2259
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2260
root_entry_direntry = ('', '', 'a-root-value'), [
2261
('d', '', 0, False, packed_stat),
2262
('d', '', 0, False, packed_stat),
2263
('d', '', 0, False, packed_stat),
2265
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2266
('d', '', 0, False, packed_stat),
2267
('d', '', 0, False, packed_stat),
2270
dirblocks.append(('', [root_entry_direntry]))
2271
dirblocks.append(('', []))
2273
state = self.create_empty_dirstate()
2274
self.addCleanup(state.unlock)
2275
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2278
# This should strip of the extra column
2279
state._discard_merge_parents()
2281
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2282
self.assertEqual(expected_dirblocks, state._dirblocks)
2284
def test_discard_absent(self):
2285
"""If entries are only in a merge, discard should remove the entries"""
2286
null_stat = dirstate.DirState.NULLSTAT
2287
present_dir = ('d', '', 0, False, null_stat)
2288
present_file = ('f', '', 0, False, null_stat)
2289
absent = dirstate.DirState.NULL_PARENT_DETAILS
2290
root_key = ('', '', 'a-root-value')
2291
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2292
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2293
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2294
('', [(file_in_merged_key,
2295
[absent, absent, present_file]),
2297
[present_file, present_file, present_file]),
2301
state = self.create_empty_dirstate()
2302
self.addCleanup(state.unlock)
2303
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2306
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2307
('', [(file_in_root_key,
2308
[present_file, present_file]),
2311
state._discard_merge_parents()
2313
self.assertEqual(exp_dirblocks, state._dirblocks)
2315
def test_discard_renamed(self):
2316
null_stat = dirstate.DirState.NULLSTAT
2317
present_dir = ('d', '', 0, False, null_stat)
2318
present_file = ('f', '', 0, False, null_stat)
2319
absent = dirstate.DirState.NULL_PARENT_DETAILS
2320
root_key = ('', '', 'a-root-value')
2321
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2322
# Renamed relative to parent
2323
file_rename_s_key = ('', 'file-s', 'b-file-id')
2324
file_rename_t_key = ('', 'file-t', 'b-file-id')
2325
# And one that is renamed between the parents, but absent in this
2326
key_in_1 = ('', 'file-in-1', 'c-file-id')
2327
key_in_2 = ('', 'file-in-2', 'c-file-id')
2330
('', [(root_key, [present_dir, present_dir, present_dir])]),
2332
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2334
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2336
[present_file, present_file, present_file]),
2338
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2340
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2344
('', [(root_key, [present_dir, present_dir])]),
2345
('', [(key_in_1, [absent, present_file]),
2346
(file_in_root_key, [present_file, present_file]),
2347
(file_rename_t_key, [present_file, absent]),
2350
state = self.create_empty_dirstate()
2351
self.addCleanup(state.unlock)
2352
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2355
state._discard_merge_parents()
2357
self.assertEqual(exp_dirblocks, state._dirblocks)
2359
def test_discard_all_subdir(self):
2360
null_stat = dirstate.DirState.NULLSTAT
2361
present_dir = ('d', '', 0, False, null_stat)
2362
present_file = ('f', '', 0, False, null_stat)
2363
absent = dirstate.DirState.NULL_PARENT_DETAILS
2364
root_key = ('', '', 'a-root-value')
2365
subdir_key = ('', 'sub', 'dir-id')
2366
child1_key = ('sub', 'child1', 'child1-id')
2367
child2_key = ('sub', 'child2', 'child2-id')
2368
child3_key = ('sub', 'child3', 'child3-id')
2371
('', [(root_key, [present_dir, present_dir, present_dir])]),
2372
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2373
('sub', [(child1_key, [absent, absent, present_file]),
2374
(child2_key, [absent, absent, present_file]),
2375
(child3_key, [absent, absent, present_file]),
2379
('', [(root_key, [present_dir, present_dir])]),
2380
('', [(subdir_key, [present_dir, present_dir])]),
2383
state = self.create_empty_dirstate()
2384
self.addCleanup(state.unlock)
2385
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2388
state._discard_merge_parents()
2390
self.assertEqual(exp_dirblocks, state._dirblocks)
2393
class Test_InvEntryToDetails(tests.TestCase):
2395
def assertDetails(self, expected, inv_entry):
2396
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2397
self.assertEqual(expected, details)
2398
# details should always allow join() and always be a plain str when
2400
(minikind, fingerprint, size, executable, tree_data) = details
2401
self.assertIsInstance(minikind, str)
2402
self.assertIsInstance(fingerprint, str)
2403
self.assertIsInstance(tree_data, str)
2405
def test_unicode_symlink(self):
2406
inv_entry = inventory.InventoryLink('link-file-id',
2407
u'nam\N{Euro Sign}e',
2409
inv_entry.revision = 'link-revision-id'
2410
target = u'link-targ\N{Euro Sign}t'
2411
inv_entry.symlink_target = target
2412
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2413
'link-revision-id'), inv_entry)
2416
class TestSHA1Provider(tests.TestCaseInTempDir):
2418
def test_sha1provider_is_an_interface(self):
2419
p = dirstate.SHA1Provider()
2420
self.assertRaises(NotImplementedError, p.sha1, "foo")
2421
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2423
def test_defaultsha1provider_sha1(self):
2424
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2425
self.build_tree_contents([('foo', text)])
2426
expected_sha = osutils.sha_string(text)
2427
p = dirstate.DefaultSHA1Provider()
2428
self.assertEqual(expected_sha, p.sha1('foo'))
2430
def test_defaultsha1provider_stat_and_sha1(self):
2431
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2432
self.build_tree_contents([('foo', text)])
2433
expected_sha = osutils.sha_string(text)
2434
p = dirstate.DefaultSHA1Provider()
2435
statvalue, sha1 = p.stat_and_sha1('foo')
2436
self.assertTrue(len(statvalue) >= 10)
2437
self.assertEqual(len(text), statvalue.st_size)
2438
self.assertEqual(expected_sha, sha1)
2441
class _Repo(object):
2442
"""A minimal api to get InventoryRevisionTree to work."""
2445
default_format = controldir.format_registry.make_bzrdir('default')
2446
self._format = default_format.repository_format
2448
def lock_read(self):
2455
class TestUpdateBasisByDelta(tests.TestCase):
2457
def path_to_ie(self, path, file_id, rev_id, dir_ids):
2458
if path.endswith('/'):
2463
dirname, basename = osutils.split(path)
2465
dir_id = dir_ids[dirname]
2467
dir_id = osutils.basename(dirname) + '-id'
2469
ie = inventory.InventoryDirectory(file_id, basename, dir_id)
2470
dir_ids[path] = file_id
2472
ie = inventory.InventoryFile(file_id, basename, dir_id)
2475
ie.revision = rev_id
2478
def create_tree_from_shape(self, rev_id, shape):
2479
dir_ids = {'': 'root-id'}
2480
inv = inventory.Inventory('root-id', rev_id)
2483
path, file_id = info
2486
path, file_id, ie_rev_id = info
2488
# Replace the root entry
2489
del inv._byid[inv.root.file_id]
2490
inv.root.file_id = file_id
2491
inv._byid[file_id] = inv.root
2492
dir_ids[''] = file_id
2494
inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))
2495
return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)
2497
def create_empty_dirstate(self):
2498
fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
2499
self.addCleanup(os.remove, path)
2501
state = dirstate.DirState.initialize(path)
2502
self.addCleanup(state.unlock)
2505
def create_inv_delta(self, delta, rev_id):
2506
"""Translate a 'delta shape' into an actual InventoryDelta"""
2507
dir_ids = {'': 'root-id'}
2509
for old_path, new_path, file_id in delta:
2510
if old_path is not None and old_path.endswith('/'):
2511
# Don't have to actually do anything for this, because only
2512
# new_path creates InventoryEntries
2513
old_path = old_path[:-1]
2514
if new_path is None: # Delete
2515
inv_delta.append((old_path, None, file_id, None))
2517
ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
2518
inv_delta.append((old_path, new_path, file_id, ie))
2521
def assertUpdate(self, active, basis, target):
2522
"""Assert that update_basis_by_delta works how we want.
2524
Set up a DirState object with active_shape for tree 0, basis_shape for
2525
tree 1. Then apply the delta from basis_shape to target_shape,
2526
and assert that the DirState is still valid, and that its stored
2527
content matches the target_shape.
2529
active_tree = self.create_tree_from_shape('active', active)
2530
basis_tree = self.create_tree_from_shape('basis', basis)
2531
target_tree = self.create_tree_from_shape('target', target)
2532
state = self.create_empty_dirstate()
2533
state.set_state_from_scratch(active_tree.root_inventory,
2534
[('basis', basis_tree)], [])
2535
delta = target_tree.root_inventory._make_delta(
2536
basis_tree.root_inventory)
2537
state.update_basis_by_delta(delta, 'target')
2539
dirstate_tree = workingtree_4.DirStateRevisionTree(state,
2541
# The target now that delta has been applied should match the
2543
self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
2544
# And the dirblock state should be identical to the state if we created
2546
state2 = self.create_empty_dirstate()
2547
state2.set_state_from_scratch(active_tree.root_inventory,
2548
[('target', target_tree)], [])
2549
self.assertEqual(state2._dirblocks, state._dirblocks)
2552
def assertBadDelta(self, active, basis, delta):
2553
"""Test that we raise InconsistentDelta when appropriate.
2555
:param active: The active tree shape
2556
:param basis: The basis tree shape
2557
:param delta: A description of the delta to apply. Similar to the form
2558
for regular inventory deltas, but omitting the InventoryEntry.
2559
So adding a file is: (None, 'path', 'file-id')
2560
Adding a directory is: (None, 'path/', 'dir-id')
2561
Renaming a dir is: ('old/', 'new/', 'dir-id')
2564
active_tree = self.create_tree_from_shape('active', active)
2565
basis_tree = self.create_tree_from_shape('basis', basis)
2566
inv_delta = self.create_inv_delta(delta, 'target')
2567
state = self.create_empty_dirstate()
2568
state.set_state_from_scratch(active_tree.root_inventory,
2569
[('basis', basis_tree)], [])
2570
self.assertRaises(errors.InconsistentDelta,
2571
state.update_basis_by_delta, inv_delta, 'target')
2573
## state.update_basis_by_delta(inv_delta, 'target')
2574
## except errors.InconsistentDelta, e:
2575
## import pdb; pdb.set_trace()
2577
## import pdb; pdb.set_trace()
2578
self.assertTrue(state._changes_aborted)
2580
def test_remove_file_matching_active_state(self):
2581
state = self.assertUpdate(
2583
basis =[('file', 'file-id')],
2587
def test_remove_file_present_in_active_state(self):
2588
state = self.assertUpdate(
2589
active=[('file', 'file-id')],
2590
basis =[('file', 'file-id')],
2594
def test_remove_file_present_elsewhere_in_active_state(self):
2595
state = self.assertUpdate(
2596
active=[('other-file', 'file-id')],
2597
basis =[('file', 'file-id')],
2601
def test_remove_file_active_state_has_diff_file(self):
2602
state = self.assertUpdate(
2603
active=[('file', 'file-id-2')],
2604
basis =[('file', 'file-id')],
2608
def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
2609
state = self.assertUpdate(
2610
active=[('file', 'file-id-2'),
2611
('other-file', 'file-id')],
2612
basis =[('file', 'file-id')],
2616
def test_add_file_matching_active_state(self):
2617
state = self.assertUpdate(
2618
active=[('file', 'file-id')],
2620
target=[('file', 'file-id')],
2623
def test_add_file_in_empty_dir_not_matching_active_state(self):
2624
state = self.assertUpdate(
2626
basis=[('dir/', 'dir-id')],
2627
target=[('dir/', 'dir-id', 'basis'), ('dir/file', 'file-id')],
2630
def test_add_file_missing_in_active_state(self):
2631
state = self.assertUpdate(
2634
target=[('file', 'file-id')],
2637
def test_add_file_elsewhere_in_active_state(self):
2638
state = self.assertUpdate(
2639
active=[('other-file', 'file-id')],
2641
target=[('file', 'file-id')],
2644
def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
2645
state = self.assertUpdate(
2646
active=[('other-file', 'file-id'),
2647
('file', 'file-id-2')],
2649
target=[('file', 'file-id')],
2652
def test_rename_file_matching_active_state(self):
2653
state = self.assertUpdate(
2654
active=[('other-file', 'file-id')],
2655
basis =[('file', 'file-id')],
2656
target=[('other-file', 'file-id')],
2659
def test_rename_file_missing_in_active_state(self):
2660
state = self.assertUpdate(
2662
basis =[('file', 'file-id')],
2663
target=[('other-file', 'file-id')],
2666
def test_rename_file_present_elsewhere_in_active_state(self):
2667
state = self.assertUpdate(
2668
active=[('third', 'file-id')],
2669
basis =[('file', 'file-id')],
2670
target=[('other-file', 'file-id')],
2673
def test_rename_file_active_state_has_diff_source_file(self):
2674
state = self.assertUpdate(
2675
active=[('file', 'file-id-2')],
2676
basis =[('file', 'file-id')],
2677
target=[('other-file', 'file-id')],
2680
def test_rename_file_active_state_has_diff_target_file(self):
2681
state = self.assertUpdate(
2682
active=[('other-file', 'file-id-2')],
2683
basis =[('file', 'file-id')],
2684
target=[('other-file', 'file-id')],
2687
def test_rename_file_active_has_swapped_files(self):
2688
state = self.assertUpdate(
2689
active=[('file', 'file-id'),
2690
('other-file', 'file-id-2')],
2691
basis= [('file', 'file-id'),
2692
('other-file', 'file-id-2')],
2693
target=[('file', 'file-id-2'),
2694
('other-file', 'file-id')])
2696
def test_rename_file_basis_has_swapped_files(self):
2697
state = self.assertUpdate(
2698
active=[('file', 'file-id'),
2699
('other-file', 'file-id-2')],
2700
basis= [('file', 'file-id-2'),
2701
('other-file', 'file-id')],
2702
target=[('file', 'file-id'),
2703
('other-file', 'file-id-2')])
2705
def test_rename_directory_with_contents(self):
2706
state = self.assertUpdate( # active matches basis
2707
active=[('dir1/', 'dir-id'),
2708
('dir1/file', 'file-id')],
2709
basis= [('dir1/', 'dir-id'),
2710
('dir1/file', 'file-id')],
2711
target=[('dir2/', 'dir-id'),
2712
('dir2/file', 'file-id')])
2713
state = self.assertUpdate( # active matches target
2714
active=[('dir2/', 'dir-id'),
2715
('dir2/file', 'file-id')],
2716
basis= [('dir1/', 'dir-id'),
2717
('dir1/file', 'file-id')],
2718
target=[('dir2/', 'dir-id'),
2719
('dir2/file', 'file-id')])
2720
state = self.assertUpdate( # active empty
2722
basis= [('dir1/', 'dir-id'),
2723
('dir1/file', 'file-id')],
2724
target=[('dir2/', 'dir-id'),
2725
('dir2/file', 'file-id')])
2726
state = self.assertUpdate( # active present at other location
2727
active=[('dir3/', 'dir-id'),
2728
('dir3/file', 'file-id')],
2729
basis= [('dir1/', 'dir-id'),
2730
('dir1/file', 'file-id')],
2731
target=[('dir2/', 'dir-id'),
2732
('dir2/file', 'file-id')])
2733
state = self.assertUpdate( # active has different ids
2734
active=[('dir1/', 'dir1-id'),
2735
('dir1/file', 'file1-id'),
2736
('dir2/', 'dir2-id'),
2737
('dir2/file', 'file2-id')],
2738
basis= [('dir1/', 'dir-id'),
2739
('dir1/file', 'file-id')],
2740
target=[('dir2/', 'dir-id'),
2741
('dir2/file', 'file-id')])
2743
def test_invalid_file_not_present(self):
2744
state = self.assertBadDelta(
2745
active=[('file', 'file-id')],
2746
basis= [('file', 'file-id')],
2747
delta=[('other-file', 'file', 'file-id')])
2749
def test_invalid_new_id_same_path(self):
2750
# The bad entry comes after
2751
state = self.assertBadDelta(
2752
active=[('file', 'file-id')],
2753
basis= [('file', 'file-id')],
2754
delta=[(None, 'file', 'file-id-2')])
2755
# The bad entry comes first
2756
state = self.assertBadDelta(
2757
active=[('file', 'file-id-2')],
2758
basis=[('file', 'file-id-2')],
2759
delta=[(None, 'file', 'file-id')])
2761
def test_invalid_existing_id(self):
2762
state = self.assertBadDelta(
2763
active=[('file', 'file-id')],
2764
basis= [('file', 'file-id')],
2765
delta=[(None, 'file', 'file-id')])
2767
def test_invalid_parent_missing(self):
2768
state = self.assertBadDelta(
2771
delta=[(None, 'path/path2', 'file-id')])
2772
# Note: we force the active tree to have the directory, by knowing how
2773
# path_to_ie handles entries with missing parents
2774
state = self.assertBadDelta(
2775
active=[('path/', 'path-id')],
2777
delta=[(None, 'path/path2', 'file-id')])
2778
state = self.assertBadDelta(
2779
active=[('path/', 'path-id'),
2780
('path/path2', 'file-id')],
2782
delta=[(None, 'path/path2', 'file-id')])
2784
def test_renamed_dir_same_path(self):
2785
# We replace the parent directory, with another parent dir. But the C
2786
# file doesn't look like it has been moved.
2787
state = self.assertUpdate(# Same as basis
2788
active=[('dir/', 'A-id'),
2790
basis= [('dir/', 'A-id'),
2792
target=[('dir/', 'C-id'),
2794
state = self.assertUpdate(# Same as target
2795
active=[('dir/', 'C-id'),
2797
basis= [('dir/', 'A-id'),
2799
target=[('dir/', 'C-id'),
2801
state = self.assertUpdate(# empty active
2803
basis= [('dir/', 'A-id'),
2805
target=[('dir/', 'C-id'),
2807
state = self.assertUpdate(# different active
2808
active=[('dir/', 'D-id'),
2810
basis= [('dir/', 'A-id'),
2812
target=[('dir/', 'C-id'),
2815
def test_parent_child_swap(self):
2816
state = self.assertUpdate(# Same as basis
2817
active=[('A/', 'A-id'),
2820
basis= [('A/', 'A-id'),
2823
target=[('A/', 'B-id'),
2826
state = self.assertUpdate(# Same as target
2827
active=[('A/', 'B-id'),
2830
basis= [('A/', 'A-id'),
2833
target=[('A/', 'B-id'),
2836
state = self.assertUpdate(# empty active
2838
basis= [('A/', 'A-id'),
2841
target=[('A/', 'B-id'),
2844
state = self.assertUpdate(# different active
2845
active=[('D/', 'A-id'),
2848
basis= [('A/', 'A-id'),
2851
target=[('A/', 'B-id'),
2855
def test_change_root_id(self):
2856
state = self.assertUpdate( # same as basis
2857
active=[('', 'root-id'),
2858
('file', 'file-id')],
2859
basis= [('', 'root-id'),
2860
('file', 'file-id')],
2861
target=[('', 'target-root-id'),
2862
('file', 'file-id')])
2863
state = self.assertUpdate( # same as target
2864
active=[('', 'target-root-id'),
2865
('file', 'file-id')],
2866
basis= [('', 'root-id'),
2867
('file', 'file-id')],
2868
target=[('', 'target-root-id'),
2869
('file', 'root-id')])
2870
state = self.assertUpdate( # all different
2871
active=[('', 'active-root-id'),
2872
('file', 'file-id')],
2873
basis= [('', 'root-id'),
2874
('file', 'file-id')],
2875
target=[('', 'target-root-id'),
2876
('file', 'root-id')])
2878
def test_change_file_absent_in_active(self):
2879
state = self.assertUpdate(
2881
basis= [('file', 'file-id')],
2882
target=[('file', 'file-id')])
2884
def test_invalid_changed_file(self):
2885
state = self.assertBadDelta( # Not present in basis
2886
active=[('file', 'file-id')],
2888
delta=[('file', 'file', 'file-id')])
2889
state = self.assertBadDelta( # present at another location in basis
2890
active=[('file', 'file-id')],
2891
basis= [('other-file', 'file-id')],
2892
delta=[('file', 'file', 'file-id')])