1
# Copyright (C) 2005, 2006 Canonical Ltd
2
# Authors: Robert Collins <robert.collins@canonical.com>
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
"""Tests for WorkingTreeFormat4"""
31
from bzrlib.lockdir import LockDir
32
from bzrlib.tests import TestCaseWithTransport, TestSkipped
33
from bzrlib.tree import InterTree
36
class TestWorkingTreeFormat4(TestCaseWithTransport):
37
"""Tests specific to WorkingTreeFormat4."""
39
def test_disk_layout(self):
40
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
41
control.create_repository()
42
control.create_branch()
43
tree = workingtree_4.WorkingTreeFormat4().initialize(control)
45
# format 'Bazaar Working Tree format 4'
47
t = control.get_workingtree_transport(None)
48
self.assertEqualDiff('Bazaar Working Tree Format 4 (bzr 0.15)\n',
49
t.get('format').read())
50
self.assertFalse(t.has('inventory.basis'))
51
# no last-revision file means 'None' or 'NULLREVISION'
52
self.assertFalse(t.has('last-revision'))
53
state = dirstate.DirState.on_file(t.local_abspath('dirstate'))
56
self.assertEqual([], state.get_parent_ids())
60
def test_uses_lockdir(self):
61
"""WorkingTreeFormat4 uses its own LockDir:
64
- when the WorkingTree is locked, LockDir can see that
66
# this test could be factored into a subclass of tests common to both
67
# format 3 and 4, but for now its not much of an issue as there is only one in common.
68
t = self.get_transport()
69
tree = self.make_workingtree()
70
self.assertIsDirectory('.bzr', t)
71
self.assertIsDirectory('.bzr/checkout', t)
72
self.assertIsDirectory('.bzr/checkout/lock', t)
73
our_lock = LockDir(t, '.bzr/checkout/lock')
74
self.assertEquals(our_lock.peek(), None)
76
self.assertTrue(our_lock.peek())
78
self.assertEquals(our_lock.peek(), None)
80
def make_workingtree(self, relpath=''):
81
url = self.get_url(relpath)
83
self.build_tree([relpath + '/'])
84
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
85
repo = dir.create_repository()
86
branch = dir.create_branch()
88
return workingtree_4.WorkingTreeFormat4().initialize(dir)
89
except errors.NotLocalUrl:
90
raise TestSkipped('Not a local URL')
92
def test_dirstate_stores_all_parent_inventories(self):
93
tree = self.make_workingtree()
95
# We're going to build in tree a working tree
96
# with three parent trees, with some files in common.
98
# We really don't want to do commit or merge in the new dirstate-based
99
# tree, because that might not work yet. So instead we build
100
# revisions elsewhere and pull them across, doing by hand part of the
101
# work that merge would do.
103
subtree = self.make_branch_and_tree('subdir')
104
# writelock the tree so its repository doesn't get readlocked by
105
# the revision tree locks. This works around the bug where we dont
106
# permit lock upgrading.
108
self.addCleanup(subtree.unlock)
109
self.build_tree(['subdir/file-a',])
110
subtree.add(['file-a'], ['id-a'])
111
rev1 = subtree.commit('commit in subdir')
113
subtree2 = subtree.bzrdir.sprout('subdir2').open_workingtree()
114
self.build_tree(['subdir2/file-b'])
115
subtree2.add(['file-b'], ['id-b'])
116
rev2 = subtree2.commit('commit in subdir2')
119
subtree3 = subtree.bzrdir.sprout('subdir3').open_workingtree()
120
rev3 = subtree3.commit('merge from subdir2')
122
repo = tree.branch.repository
123
repo.fetch(subtree.branch.repository, rev1)
124
repo.fetch(subtree2.branch.repository, rev2)
125
repo.fetch(subtree3.branch.repository, rev3)
126
# will also pull the others...
128
# create repository based revision trees
129
rev1_revtree = repo.revision_tree(rev1)
130
rev2_revtree = repo.revision_tree(rev2)
131
rev3_revtree = repo.revision_tree(rev3)
132
# tree doesn't contain a text merge yet but we'll just
133
# set the parents as if a merge had taken place.
134
# this should cause the tree data to be folded into the
136
tree.set_parent_trees([
137
(rev1, rev1_revtree),
138
(rev2, rev2_revtree),
139
(rev3, rev3_revtree), ])
141
# create tree-sourced revision trees
142
rev1_tree = tree.revision_tree(rev1)
143
rev1_tree.lock_read()
144
self.addCleanup(rev1_tree.unlock)
145
rev2_tree = tree.revision_tree(rev2)
146
rev2_tree.lock_read()
147
self.addCleanup(rev2_tree.unlock)
148
rev3_tree = tree.revision_tree(rev3)
149
rev3_tree.lock_read()
150
self.addCleanup(rev3_tree.unlock)
152
# now we should be able to get them back out
153
self.assertTreesEqual(rev1_revtree, rev1_tree)
154
self.assertTreesEqual(rev2_revtree, rev2_tree)
155
self.assertTreesEqual(rev3_revtree, rev3_tree)
157
def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
158
"""Setting parent trees on a dirstate working tree takes
159
the trees it's given and doesn't need to read them from the
162
tree = self.make_workingtree()
164
subtree = self.make_branch_and_tree('subdir')
165
rev1 = subtree.commit('commit in subdir')
166
rev1_tree = subtree.basis_tree()
167
rev1_tree.lock_read()
168
self.addCleanup(rev1_tree.unlock)
170
tree.branch.pull(subtree.branch)
172
# break the repository's legs to make sure it only uses the trees
173
# it's given; any calls to forbidden methods will raise an
175
repo = tree.branch.repository
176
repo.get_revision = self.fail
177
repo.get_inventory = self.fail
178
repo.get_inventory_xml = self.fail
179
# try to set the parent trees.
180
tree.set_parent_trees([(rev1, rev1_tree)])
182
def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
183
"""Getting parent trees from a dirstate tree does not read from the
184
repos inventory store. This is an important part of the dirstate
185
performance optimisation work.
187
tree = self.make_workingtree()
189
subtree = self.make_branch_and_tree('subdir')
190
# writelock the tree so its repository doesn't get readlocked by
191
# the revision tree locks. This works around the bug where we dont
192
# permit lock upgrading.
194
self.addCleanup(subtree.unlock)
195
rev1 = subtree.commit('commit in subdir')
196
rev1_tree = subtree.basis_tree()
197
rev1_tree.lock_read()
199
self.addCleanup(rev1_tree.unlock)
200
rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
201
rev2_tree = subtree.basis_tree()
202
rev2_tree.lock_read()
204
self.addCleanup(rev2_tree.unlock)
206
tree.branch.pull(subtree.branch)
208
# break the repository's legs to make sure it only uses the trees
209
# it's given; any calls to forbidden methods will raise an
211
repo = tree.branch.repository
212
# dont uncomment this: the revision object must be accessed to
213
# answer 'get_parent_ids' for the revision tree- dirstate does not
214
# cache the parents of a parent tree at this point.
215
#repo.get_revision = self.fail
216
repo.get_inventory = self.fail
217
repo.get_inventory_xml = self.fail
218
# set the parent trees.
219
tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
220
# read the first tree
221
result_rev1_tree = tree.revision_tree(rev1)
223
result_rev2_tree = tree.revision_tree(rev2)
224
# compare - there should be no differences between the handed and
226
self.assertTreesEqual(rev1_tree, result_rev1_tree)
227
self.assertTreesEqual(rev2_tree, result_rev2_tree)
229
def test_dirstate_doesnt_cache_non_parent_trees(self):
230
"""Getting parent trees from a dirstate tree does not read from the
231
repos inventory store. This is an important part of the dirstate
232
performance optimisation work.
234
tree = self.make_workingtree()
236
# make a tree that we can try for, which is able to be returned but
238
subtree = self.make_branch_and_tree('subdir')
239
rev1 = subtree.commit('commit in subdir')
240
tree.branch.pull(subtree.branch)
242
self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
244
def test_no_dirstate_outside_lock(self):
245
# temporary test until the code is mature enough to test from outside.
246
"""Getting a dirstate object fails if there is no lock."""
247
def lock_and_call_current_dirstate(tree, lock_method):
248
getattr(tree, lock_method)()
249
tree.current_dirstate()
251
tree = self.make_workingtree()
252
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
253
lock_and_call_current_dirstate(tree, 'lock_read')
254
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
255
lock_and_call_current_dirstate(tree, 'lock_write')
256
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
257
lock_and_call_current_dirstate(tree, 'lock_tree_write')
258
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
260
def test_new_dirstate_on_new_lock(self):
261
# until we have detection for when a dirstate can be reused, we
262
# want to reparse dirstate on every new lock.
263
known_dirstates = set()
264
def lock_and_compare_all_current_dirstate(tree, lock_method):
265
getattr(tree, lock_method)()
266
state = tree.current_dirstate()
267
self.assertFalse(state in known_dirstates)
268
known_dirstates.add(state)
270
tree = self.make_workingtree()
271
# lock twice with each type to prevent silly per-lock-type bugs.
272
# each lock and compare looks for a unique state object.
273
lock_and_compare_all_current_dirstate(tree, 'lock_read')
274
lock_and_compare_all_current_dirstate(tree, 'lock_read')
275
lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
276
lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
277
lock_and_compare_all_current_dirstate(tree, 'lock_write')
278
lock_and_compare_all_current_dirstate(tree, 'lock_write')
280
def test_constructing_invalid_interdirstate_raises(self):
281
tree = self.make_workingtree()
282
rev_id = tree.commit('first post')
283
rev_id2 = tree.commit('second post')
284
rev_tree = tree.branch.repository.revision_tree(rev_id)
285
# Exception is not a great thing to raise, but this test is
286
# very short, and code is used to sanity check other tests, so
287
# a full error object is YAGNI.
289
Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
291
Exception, workingtree_4.InterDirStateTree, tree, rev_tree)
293
def test_revtree_to_revtree_not_interdirstate(self):
294
# we should not get a dirstate optimiser for two repository sourced
295
# revtrees. we can't prove a negative, so we dont do exhaustive tests
296
# of all formats; though that could be written in the future it doesn't
297
# seem well worth it.
298
tree = self.make_workingtree()
299
rev_id = tree.commit('first post')
300
rev_id2 = tree.commit('second post')
301
rev_tree = tree.branch.repository.revision_tree(rev_id)
302
rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
303
optimiser = InterTree.get(rev_tree, rev_tree2)
304
self.assertIsInstance(optimiser, InterTree)
305
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
306
optimiser = InterTree.get(rev_tree2, rev_tree)
307
self.assertIsInstance(optimiser, InterTree)
308
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
310
def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
311
# we should not get a dirstate optimiser when the revision id for of
312
# the source is not in the dirstate of the target.
313
tree = self.make_workingtree()
314
rev_id = tree.commit('first post')
315
rev_id2 = tree.commit('second post')
316
rev_tree = tree.branch.repository.revision_tree(rev_id)
318
optimiser = InterTree.get(rev_tree, tree)
319
self.assertIsInstance(optimiser, InterTree)
320
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
321
optimiser = InterTree.get(tree, rev_tree)
322
self.assertIsInstance(optimiser, InterTree)
323
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
326
def test_empty_basis_to_dirstate_tree(self):
327
# we should get a InterDirStateTree for doing
328
# 'changes_from' from the first basis dirstate revision tree to a
330
tree = self.make_workingtree()
332
basis_tree = tree.basis_tree()
333
basis_tree.lock_read()
334
optimiser = InterTree.get(basis_tree, tree)
337
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
339
def test_nonempty_basis_to_dirstate_tree(self):
340
# we should get a InterDirStateTree for doing
341
# 'changes_from' from a non-null basis dirstate revision tree to a
343
tree = self.make_workingtree()
344
tree.commit('first post')
346
basis_tree = tree.basis_tree()
347
basis_tree.lock_read()
348
optimiser = InterTree.get(basis_tree, tree)
351
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
353
def test_empty_basis_revtree_to_dirstate_tree(self):
354
# we should get a InterDirStateTree for doing
355
# 'changes_from' from an empty repository based rev tree to a
357
tree = self.make_workingtree()
359
basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
360
basis_tree.lock_read()
361
optimiser = InterTree.get(basis_tree, tree)
364
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
366
def test_nonempty_basis_revtree_to_dirstate_tree(self):
367
# we should get a InterDirStateTree for doing
368
# 'changes_from' from a non-null repository based rev tree to a
370
tree = self.make_workingtree()
371
tree.commit('first post')
373
basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
374
basis_tree.lock_read()
375
optimiser = InterTree.get(basis_tree, tree)
378
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
380
def test_tree_to_basis_in_other_tree(self):
381
# we should get a InterDirStateTree when
382
# the source revid is in the dirstate object of the target and
383
# the dirstates are different. This is largely covered by testing
384
# with repository revtrees, so is just for extra confidence.
385
tree = self.make_workingtree('a')
386
tree.commit('first post')
387
tree2 = self.make_workingtree('b')
388
tree2.pull(tree.branch)
389
basis_tree = tree.basis_tree()
391
basis_tree.lock_read()
392
optimiser = InterTree.get(basis_tree, tree2)
395
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
397
def test_merged_revtree_to_tree(self):
398
# we should get a InterDirStateTree when
399
# the source tree is a merged tree present in the dirstate of target.
400
tree = self.make_workingtree('a')
401
tree.commit('first post')
402
tree.commit('tree 1 commit 2')
403
tree2 = self.make_workingtree('b')
404
tree2.pull(tree.branch)
405
tree2.commit('tree 2 commit 2')
406
tree.merge_from_branch(tree2.branch)
407
second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
408
second_parent_tree.lock_read()
410
optimiser = InterTree.get(second_parent_tree, tree)
412
second_parent_tree.unlock()
413
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
415
def test_id2path(self):
416
tree = self.make_workingtree('tree')
417
self.build_tree(['tree/a', 'tree/b'])
418
tree.add(['a'], ['a-id'])
419
self.assertEqual(u'a', tree.id2path('a-id'))
420
self.assertRaises(errors.NoSuchId, tree.id2path, 'a')
422
tree.add(['b'], ['b-id'])
425
new_path = u'b\u03bcrry'
426
tree.rename_one('a', new_path)
427
except UnicodeEncodeError:
428
# support running the test on non-unicode platforms
430
tree.rename_one('a', new_path)
431
self.assertEqual(new_path, tree.id2path('a-id'))
432
tree.commit(u'b\xb5rry')
433
tree.unversion(['a-id'])
434
self.assertRaises(errors.NoSuchId, tree.id2path, 'a-id')
435
self.assertEqual('b', tree.id2path('b-id'))
436
self.assertRaises(errors.NoSuchId, tree.id2path, 'c-id')
438
def test_unique_root_id_per_tree(self):
439
# each time you initialize a new tree, it gets a different root id
440
format_name = 'dirstate-with-subtree'
441
tree1 = self.make_branch_and_tree('tree1',
443
tree2 = self.make_branch_and_tree('tree2',
445
self.assertNotEqual(tree1.get_root_id(), tree2.get_root_id())
446
# when you branch, it inherits the same root id
447
rev1 = tree1.commit('first post')
448
tree3 = tree1.bzrdir.sprout('tree3').open_workingtree()
449
self.assertEqual(tree3.get_root_id(), tree1.get_root_id())
451
def test_set_root_id(self):
452
# similar to some code that fails in the dirstate-plus-subtree branch
453
# -- setting the root id while adding a parent seems to scramble the
454
# dirstate invariants. -- mbp 20070303
458
wt.current_dirstate()._validate()
461
wt = self.make_workingtree('tree')
462
wt.set_root_id('TREE-ROOTID')
464
wt.commit('somenthing')
466
# now switch and commit again
467
wt.set_root_id('tree-rootid')
472
def test_default_root_id(self):
473
tree = self.make_branch_and_tree('tag', format='dirstate-tags')
474
self.assertEqual(inventory.ROOT_ID, tree.get_root_id())
475
tree = self.make_branch_and_tree('subtree',
476
format='dirstate-with-subtree')
477
self.assertNotEqual(inventory.ROOT_ID, tree.get_root_id())
479
def test_non_subtree_with_nested_trees(self):
480
# prior to dirstate, st/diff/commit ignored nested trees.
481
# dirstate, as opposed to dirstate-with-subtree, should
482
# behave the same way.
483
tree = self.make_branch_and_tree('.', format='dirstate')
484
self.assertFalse(tree.supports_tree_reference())
485
self.build_tree(['dir/'])
486
# for testing easily.
487
tree.set_root_id('root')
488
tree.add(['dir'], ['dir-id'])
489
subtree = self.make_branch_and_tree('dir')
490
# the most primitive operation: kind
491
self.assertEqual('directory', tree.kind('dir-id'))
492
# a diff against the basis should give us a directory
494
expected = [('dir-id',
502
self.assertEqual(expected, list(tree.iter_changes(tree.basis_tree(),
503
specific_files=['dir'])))
505
# do a commit, we want to trigger the dirstate fast-path too
506
tree.commit('first post')
507
# change the path for the subdir, which will trigger getting all
509
os.rename('dir', 'also-dir')
510
# now the diff will use the fast path
512
expected = [('dir-id',
520
self.assertEqual(expected, list(tree.iter_changes(tree.basis_tree())))
523
def test_with_subtree_supports_tree_references(self):
524
# dirstate-with-subtree should support tree-references.
525
tree = self.make_branch_and_tree('.', format='dirstate-with-subtree')
526
self.assertTrue(tree.supports_tree_reference())
527
# having checked this is on, the tree interface, and intertree
528
# interface tests, will proceed to test the subtree support of
531
def test_iter_changes_ignores_unversioned_dirs(self):
532
"""iter_changes should not descend into unversioned directories."""
533
tree = self.make_branch_and_tree('.', format='dirstate')
534
# We have an unversioned directory at the root, a versioned one with
535
# other versioned files and an unversioned directory, and another
536
# versioned dir with nothing but an unversioned directory.
537
self.build_tree(['unversioned/',
541
'versioned/unversioned/',
542
'versioned/unversioned/a',
543
'versioned/unversioned/b/',
546
'versioned2/unversioned/',
547
'versioned2/unversioned/a',
548
'versioned2/unversioned/b/',
550
tree.add(['versioned', 'versioned2', 'versioned2/a'])
551
tree.commit('one', rev_id='rev-1')
552
# Trap osutils._walkdirs_utf8 to spy on what dirs have been accessed.
554
orig_walkdirs = osutils._walkdirs_utf8
556
osutils._walkdirs_utf8 = orig_walkdirs
557
self.addCleanup(reset)
558
def walkdirs_spy(*args, **kwargs):
559
for val in orig_walkdirs(*args, **kwargs):
560
returned.append(val[0][0])
562
osutils._walkdirs_utf8 = walkdirs_spy
564
basis = tree.basis_tree()
566
self.addCleanup(tree.unlock)
568
self.addCleanup(basis.unlock)
569
changes = [c[1] for c in
570
tree.iter_changes(basis, want_unversioned=True)]
571
self.assertEqual([(None, 'unversioned'),
572
(None, 'versioned/unversioned'),
573
(None, 'versioned2/unversioned'),
575
self.assertEqual(['', 'versioned', 'versioned2'], returned)
576
del returned[:] # reset
577
changes = [c[1] for c in tree.iter_changes(basis)]
578
self.assertEqual([], changes)
579
self.assertEqual(['', 'versioned', 'versioned2'], returned)
581
def get_tree_with_cachable_file_foo(self):
582
tree = self.make_branch_and_tree('.')
583
self.build_tree(['foo'])
584
tree.add(['foo'], ['foo-id'])
585
# a 4 second old timestamp is always hashable - sucks to delay
586
# the test suite, but not testing this is worse.
590
def test_commit_updates_hash_cache(self):
591
tree = self.get_tree_with_cachable_file_foo()
592
revid = tree.commit('a commit')
593
# tree's dirstate should now have a valid stat entry for foo.
595
entry = tree._get_entry(path='foo')
596
expected_sha1 = osutils.sha_file_by_name('foo')
597
self.assertEqual(expected_sha1, entry[1][0][1])
599
def test_observed_sha1_cachable(self):
600
tree = self.get_tree_with_cachable_file_foo()
601
expected_sha1 = osutils.sha_file_by_name('foo')
602
statvalue = os.lstat("foo")
605
tree._observed_sha1("foo-id", "foo", (expected_sha1, statvalue))
606
self.assertEqual(expected_sha1,
607
tree._get_entry(path="foo")[1][0][1])
610
tree = tree.bzrdir.open_workingtree()
612
self.addCleanup(tree.unlock)
613
self.assertEqual(expected_sha1, tree._get_entry(path="foo")[1][0][1])
615
def test_observed_sha1_new_file(self):
616
tree = self.make_branch_and_tree('.')
617
self.build_tree(['foo'])
618
tree.add(['foo'], ['foo-id'])
621
current_sha1 = tree._get_entry(path="foo")[1][0][1]
626
tree._observed_sha1("foo-id", "foo",
627
(osutils.sha_file_by_name('foo'), os.lstat("foo")))
628
# Must not have changed
629
self.assertEqual(current_sha1,
630
tree._get_entry(path="foo")[1][0][1])
634
def test_get_file_with_stat_id_only(self):
635
# Explicit test to ensure we get a lstat value from WT4 trees.
636
tree = self.make_branch_and_tree('.')
637
self.build_tree(['foo'])
638
tree.add(['foo'], ['foo-id'])
640
self.addCleanup(tree.unlock)
641
file_obj, statvalue = tree.get_file_with_stat('foo-id')
642
expected = os.lstat('foo')
643
self.assertEqualStat(expected, statvalue)
644
self.assertEqual(["contents of foo\n"], file_obj.readlines())
647
class TestCorruptDirstate(TestCaseWithTransport):
648
"""Tests for how we handle when the dirstate has been corrupted."""
650
def create_wt4(self):
651
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
652
control.create_repository()
653
control.create_branch()
654
tree = workingtree_4.WorkingTreeFormat4().initialize(control)
657
def test_invalid_rename(self):
658
tree = self.create_wt4()
659
# Create a corrupted dirstate
662
tree.commit('init') # We need a parent, or we always compare with NULL
663
state = tree.current_dirstate()
664
state._read_dirblocks_if_needed()
665
# Now add in an invalid entry, a rename with a dangling pointer
666
state._dirblocks[1][1].append((('', 'foo', 'foo-id'),
667
[('f', '', 0, False, ''),
668
('r', 'bar', 0 , False, '')]))
669
self.assertListRaises(errors.CorruptDirstate,
670
tree.iter_changes, tree.basis_tree())
674
def get_simple_dirblocks(self, state):
675
"""Extract the simple information from the DirState.
677
This returns the dirblocks, only with the sha1sum and stat details
681
for block in state._dirblocks:
682
simple_block = (block[0], [])
683
for entry in block[1]:
684
# Include the key for each entry, and for each parent include
685
# just the minikind, so we know if it was
686
# present/absent/renamed/etc
687
simple_block[1].append((entry[0], [i[0] for i in entry[1]]))
688
simple_blocks.append(simple_block)
691
def test_update_basis_with_invalid_delta(self):
692
"""When given an invalid delta, it should abort, and not be saved."""
693
self.build_tree(['dir/', 'dir/file'])
694
tree = self.create_wt4()
696
self.addCleanup(tree.unlock)
697
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
698
first_revision_id = tree.commit('init')
700
root_id = tree.path2id('')
701
state = tree.current_dirstate()
702
state._read_dirblocks_if_needed()
704
('', [(('', '', root_id), ['d', 'd'])]),
705
('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
706
('dir', [(('dir', 'file', 'file-id'), ['f', 'f'])]),
707
], self.get_simple_dirblocks(state))
709
tree.remove(['dir/file'])
711
('', [(('', '', root_id), ['d', 'd'])]),
712
('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
713
('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
714
], self.get_simple_dirblocks(state))
715
# Make sure the removal is written to disk
718
# self.assertRaises(Exception, tree.update_basis_by_delta,
719
new_dir = inventory.InventoryDirectory('dir-id', 'new-dir', root_id)
720
new_dir.revision = 'new-revision-id'
721
new_file = inventory.InventoryFile('file-id', 'new-file', root_id)
722
new_file.revision = 'new-revision-id'
723
self.assertRaises(errors.InconsistentDelta,
724
tree.update_basis_by_delta, 'new-revision-id',
725
[('dir', 'new-dir', 'dir-id', new_dir),
726
('dir/file', 'new-dir/new-file', 'file-id', new_file),
730
# Now when we re-read the file it should not have been modified
733
self.assertEqual(first_revision_id, tree.last_revision())
734
state = tree.current_dirstate()
735
state._read_dirblocks_if_needed()
737
('', [(('', '', root_id), ['d', 'd'])]),
738
('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
739
('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
740
], self.get_simple_dirblocks(state))