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
189
374
class TestTreeToDirState(TestCaseWithDirState):
399
773
# This will unlock it
400
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)
402
924
def test_set_path_id_no_parents(self):
403
925
"""The id of a path can be changed trivally with no parents."""
404
926
state = dirstate.DirState.initialize('dirstate')
406
928
# check precondition to be sure the state does change appropriately.
408
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
409
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
410
list(state._iter_entries()))
411
state.set_path_id('', 'foobarbaz')
413
(('', '', 'foobarbaz'), [('d', '', 0, False,
414
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
415
self.assertEqual(expected_rows, list(state._iter_entries()))
416
# should work across save too
420
state = dirstate.DirState.on_file('dirstate')
423
self.assertEqual(expected_rows, list(state._iter_entries()))
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]
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'))
946
# should work across save too
950
state = dirstate.DirState.on_file('dirstate')
954
self.assertEqual(expected_rows, list(state._iter_entries()))
958
def test_set_path_id_with_parents(self):
959
"""Set the root file id in a dirstate with parents"""
960
mt = self.make_branch_and_tree('mt')
961
# in case the default tree format uses a different root id
962
mt.set_root_id('TREE_ROOT')
963
mt.commit('foo', rev_id='parent-revid')
964
rt = mt.branch.repository.revision_tree('parent-revid')
965
state = dirstate.DirState.initialize('dirstate')
968
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
969
root_entry = (('', '', 'TREE_ROOT'),
970
[('d', '', 0, False, 'x'*32),
971
('d', '', 0, False, 'parent-revid')])
972
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
973
self.assertEqual(root_entry,
974
state._get_entry(0, fileid_utf8='TREE_ROOT'))
975
self.assertEqual((None, None),
976
state._get_entry(0, fileid_utf8='Asecond-root-id'))
977
state.set_path_id('', 'Asecond-root-id')
979
# now see that it is what we expected
980
old_root_entry = (('', '', 'TREE_ROOT'),
981
[('a', '', 0, False, ''),
982
('d', '', 0, False, 'parent-revid')])
983
new_root_entry = (('', '', 'Asecond-root-id'),
984
[('d', '', 0, False, ''),
985
('a', '', 0, False, '')])
986
expected_rows = [new_root_entry, old_root_entry]
988
self.assertEqual(expected_rows, list(state._iter_entries()))
989
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
990
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
991
self.assertEqual((None, None),
992
state._get_entry(0, fileid_utf8='TREE_ROOT'))
993
self.assertEqual(old_root_entry,
994
state._get_entry(1, fileid_utf8='TREE_ROOT'))
995
self.assertEqual(new_root_entry,
996
state._get_entry(0, fileid_utf8='Asecond-root-id'))
997
self.assertEqual((None, None),
998
state._get_entry(1, fileid_utf8='Asecond-root-id'))
999
# should work across save too
1003
# now flush & check we get the same
1004
state = dirstate.DirState.on_file('dirstate')
1008
self.assertEqual(expected_rows, list(state._iter_entries()))
1011
# now change within an existing file-backed state
1015
state.set_path_id('', 'tree-root-2')
954
1790
# *really* cheesy way to just get an empty tree
955
1791
repo = self.make_repository('repo')
956
empty_tree = repo.revision_tree(None)
1792
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
957
1793
state.set_parent_trees([('null:', empty_tree)], [])
959
1795
dirblock_names = [d[0] for d in state._dirblocks]
960
1796
self.assertEqual(expected, dirblock_names)
963
class TestBisect(TestCaseWithTransport):
1799
class InstrumentedDirState(dirstate.DirState):
1800
"""An DirState with instrumented sha1 functionality."""
1802
def __init__(self, path, sha1_provider, worth_saving_limit=0):
1803
super(InstrumentedDirState, self).__init__(path, sha1_provider,
1804
worth_saving_limit=worth_saving_limit)
1805
self._time_offset = 0
1807
# member is dynamically set in DirState.__init__ to turn on trace
1808
self._sha1_provider = sha1_provider
1809
self._sha1_file = self._sha1_file_and_log
1811
def _sha_cutoff_time(self):
1812
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1813
self._cutoff_time = timestamp + self._time_offset
1815
def _sha1_file_and_log(self, abspath):
1816
self._log.append(('sha1', abspath))
1817
return self._sha1_provider.sha1(abspath)
1819
def _read_link(self, abspath, old_link):
1820
self._log.append(('read_link', abspath, old_link))
1821
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1823
def _lstat(self, abspath, entry):
1824
self._log.append(('lstat', abspath))
1825
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1827
def _is_executable(self, mode, old_executable):
1828
self._log.append(('is_exec', mode, old_executable))
1829
return super(InstrumentedDirState, self)._is_executable(mode,
1832
def adjust_time(self, secs):
1833
"""Move the clock forward or back.
1835
:param secs: The amount to adjust the clock by. Positive values make it
1836
seem as if we are in the future, negative values make it seem like we
1839
self._time_offset += secs
1840
self._cutoff_time = None
1843
class _FakeStat(object):
1844
"""A class with the same attributes as a real stat result."""
1846
def __init__(self, size, mtime, ctime, dev, ino, mode):
1848
self.st_mtime = mtime
1849
self.st_ctime = ctime
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):
1862
def assertPackStat(self, expected, stat_value):
1863
"""Check the packed and serialized form of a stat value."""
1864
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1866
def test_pack_stat_int(self):
1867
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1868
# Make sure that all parameters have an impact on the packed stat.
1869
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1872
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1873
st.st_mtime = 1172758620
1875
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1876
st.st_ctime = 1172758630
1878
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1881
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1882
st.st_ino = 6499540L
1884
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1885
st.st_mode = 0100744
1887
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1889
def test_pack_stat_float(self):
1890
"""On some platforms mtime and ctime are floats.
1892
Make sure we don't get warnings or errors, and that we ignore changes <
1895
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1896
777L, 6499538L, 0100644)
1897
# These should all be the same as the integer counterparts
1898
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1899
st.st_mtime = 1172758620.0
1901
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1902
st.st_ctime = 1172758630.0
1904
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1905
# fractional seconds are discarded, so no change from above
1906
st.st_mtime = 1172758620.453
1907
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1908
st.st_ctime = 1172758630.228
1909
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1912
class TestBisect(TestCaseWithDirState):
964
1913
"""Test the ability to bisect into the disk format."""
966
def create_basic_dirstate(self):
967
"""Create a dirstate with a few files and directories.
976
tree = self.make_branch_and_tree('tree')
977
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
978
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
979
self.build_tree(['tree/' + p for p in paths])
980
tree.set_root_id('TREE_ROOT')
981
tree.add([p.rstrip('/') for p in paths], file_ids)
982
tree.commit('initial', rev_id='rev-1')
983
revision_id = 'rev-1'
984
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
985
t = self.get_transport().clone('tree')
986
a_text = t.get_bytes('a')
987
a_sha = osutils.sha_string(a_text)
989
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
990
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
991
c_text = t.get_bytes('b/c')
992
c_sha = osutils.sha_string(c_text)
994
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
995
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
996
e_text = t.get_bytes('b/d/e')
997
e_sha = osutils.sha_string(e_text)
999
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1000
f_text = t.get_bytes('f')
1001
f_sha = osutils.sha_string(f_text)
1003
null_stat = dirstate.DirState.NULLSTAT
1005
'':(('', '', 'TREE_ROOT'), [
1006
('d', '', 0, False, null_stat),
1007
('d', '', 0, False, revision_id),
1009
'a':(('', 'a', 'a-id'), [
1010
('f', '', 0, False, null_stat),
1011
('f', a_sha, a_len, False, revision_id),
1013
'b':(('', 'b', 'b-id'), [
1014
('d', '', 0, False, null_stat),
1015
('d', '', 0, False, revision_id),
1017
'b/c':(('b', 'c', 'c-id'), [
1018
('f', '', 0, False, null_stat),
1019
('f', c_sha, c_len, False, revision_id),
1021
'b/d':(('b', 'd', 'd-id'), [
1022
('d', '', 0, False, null_stat),
1023
('d', '', 0, False, revision_id),
1025
'b/d/e':(('b/d', 'e', 'e-id'), [
1026
('f', '', 0, False, null_stat),
1027
('f', e_sha, e_len, False, revision_id),
1029
'f':(('', 'f', 'f-id'), [
1030
('f', '', 0, False, null_stat),
1031
('f', f_sha, f_len, False, revision_id),
1034
state = dirstate.DirState.from_tree(tree, 'dirstate')
1039
# Use a different object, to make sure nothing is pre-cached in memory.
1040
state = dirstate.DirState.on_file('dirstate')
1042
self.addCleanup(state.unlock)
1043
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1044
state._dirblock_state)
1045
# This is code is only really tested if we actually have to make more
1046
# than one read, so set the page size to something smaller.
1047
# We want it to contain about 2.2 records, so that we have a couple
1048
# records that we can read per attempt
1049
state._bisect_page_size = 200
1050
return tree, state, expected
1052
def create_duplicated_dirstate(self):
1053
"""Create a dirstate with a deleted and added entries.
1055
This grabs a basic_dirstate, and then removes and re adds every entry
1058
tree, state, expected = self.create_basic_dirstate()
1059
# Now we will just remove and add every file so we get an extra entry
1060
# per entry. Unversion in reverse order so we handle subdirs
1061
tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1062
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1063
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1065
# Update the expected dictionary.
1066
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1067
orig = expected[path]
1069
# This record was deleted in the current tree
1070
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1072
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1073
# And didn't exist in the basis tree
1074
expected[path2] = (new_key, [orig[1][0],
1075
dirstate.DirState.NULL_PARENT_DETAILS])
1077
# We will replace the 'dirstate' file underneath 'state', but that is
1078
# okay as lock as we unlock 'state' first.
1081
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1087
# But we need to leave state in a read-lock because we already have
1088
# a cleanup scheduled
1090
return tree, state, expected
1092
def create_renamed_dirstate(self):
1093
"""Create a dirstate with a few internal renames.
1095
This takes the basic dirstate, and moves the paths around.
1097
tree, state, expected = self.create_basic_dirstate()
1099
tree.rename_one('a', 'b/g')
1101
tree.rename_one('b/d', 'h')
1103
old_a = expected['a']
1104
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1105
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1106
('r', 'a', 0, False, '')])
1107
old_d = expected['b/d']
1108
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1109
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1110
('r', 'b/d', 0, False, '')])
1112
old_e = expected['b/d/e']
1113
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1115
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1116
('r', 'b/d/e', 0, False, '')])
1120
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1127
return tree, state, expected
1129
1915
def assertBisect(self, expected_map, map_keys, state, paths):
1130
1916
"""Assert that bisecting for paths returns the right result.
1357
class TestBisectDirblock(TestCase):
1358
"""Test that bisect_dirblock() returns the expected values.
1360
bisect_dirblock is intended to work like bisect.bisect_left() except it
1361
knows it is working on dirblocks and that dirblocks are sorted by ('path',
1362
'to', 'foo') chunks rather than by raw 'path/to/foo'.
1365
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
1366
"""Assert that bisect_split works like bisect_left on the split paths.
1368
:param dirblocks: A list of (path, [info]) pairs.
1369
:param split_dirblocks: A list of ((split, path), [info]) pairs.
1370
:param path: The path we are indexing.
1372
All other arguments will be passed along.
1374
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
1376
split_dirblock = (path.split('/'), [])
1377
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
1379
self.assertEqual(bisect_left_idx, bisect_split_idx,
1380
'bisect_split disagreed. %s != %s'
1382
% (bisect_left_idx, bisect_split_idx, path)
1385
def paths_to_dirblocks(self, paths):
1386
"""Convert a list of paths into dirblock form.
1388
Also, ensure that the paths are in proper sorted order.
1390
dirblocks = [(path, []) for path in paths]
1391
split_dirblocks = [(path.split('/'), []) for path in paths]
1392
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
1393
return dirblocks, split_dirblocks
1395
def test_simple(self):
1396
"""In the simple case it works just like bisect_left"""
1397
paths = ['', 'a', 'b', 'c', 'd']
1398
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1400
self.assertBisect(dirblocks, split_dirblocks, path)
1401
self.assertBisect(dirblocks, split_dirblocks, '_')
1402
self.assertBisect(dirblocks, split_dirblocks, 'aa')
1403
self.assertBisect(dirblocks, split_dirblocks, 'bb')
1404
self.assertBisect(dirblocks, split_dirblocks, 'cc')
1405
self.assertBisect(dirblocks, split_dirblocks, 'dd')
1406
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
1407
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
1408
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
1409
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
1411
def test_involved(self):
1412
"""This is where bisect_left diverges slightly."""
1414
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1415
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1417
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1418
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1421
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1423
self.assertBisect(dirblocks, split_dirblocks, path)
1425
def test_involved_cached(self):
1426
"""This is where bisect_left diverges slightly."""
1428
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
1429
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
1431
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
1432
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
1436
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
1438
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')])