~bzr-pqm/bzr/bzr.dev

5752.3.8 by John Arbash Meinel
Merge bzr.dev 5764 to resolve release-notes (aka NEWS) conflicts
1
# Copyright (C) 2007-2011 Canonical Ltd
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
2
# Authors:  Robert Collins <robert.collins@canonical.com>
3
#
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.
8
#
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.
13
#
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
4183.7.1 by Sabin Iacob
update FSF mailing address
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
17
18
"""Tests for WorkingTreeFormat4"""
19
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
20
import os
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
21
import time
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
22
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
23
from bzrlib import (
24
    bzrdir,
25
    dirstate,
26
    errors,
1551.15.6 by Aaron Bentley
Use ROOT_ID when the repository supports old clients (Bug #107168)
27
    inventory,
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
28
    osutils,
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
29
    workingtree_4,
30
    )
31
from bzrlib.lockdir import LockDir
32
from bzrlib.tests import TestCaseWithTransport, TestSkipped
33
from bzrlib.tree import InterTree
34
35
36
class TestWorkingTreeFormat4(TestCaseWithTransport):
37
    """Tests specific to WorkingTreeFormat4."""
38
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)
44
        # we want:
45
        # format 'Bazaar Working Tree format 4'
46
        # stat-cache = ??
47
        t = control.get_workingtree_transport(None)
2255.2.230 by Robert Collins
Update tree format signatures to mention introducing bzr version.
48
        self.assertEqualDiff('Bazaar Working Tree Format 4 (bzr 0.15)\n',
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
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'))
54
        state.lock_read()
55
        try:
56
            self.assertEqual([], state.get_parent_ids())
57
        finally:
58
            state.unlock()
59
60
    def test_uses_lockdir(self):
61
        """WorkingTreeFormat4 uses its own LockDir:
2255.10.1 by John Arbash Meinel
Update WorkingTree4 so that it doesn't use a HashCache,
62
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
63
            - lock is a directory
64
            - when the WorkingTree is locked, LockDir can see that
65
        """
66
        # this test could be factored into a subclass of tests common to both
4285.2.1 by Vincent Ladeuil
Cleanup test imports and use features to better track skipped tests.
67
        # format 3 and 4, but for now its not much of an issue as there is only
68
        # one in common.
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
69
        t = self.get_transport()
70
        tree = self.make_workingtree()
71
        self.assertIsDirectory('.bzr', t)
72
        self.assertIsDirectory('.bzr/checkout', t)
73
        self.assertIsDirectory('.bzr/checkout/lock', t)
74
        our_lock = LockDir(t, '.bzr/checkout/lock')
75
        self.assertEquals(our_lock.peek(), None)
76
        tree.lock_write()
77
        self.assertTrue(our_lock.peek())
78
        tree.unlock()
79
        self.assertEquals(our_lock.peek(), None)
80
81
    def make_workingtree(self, relpath=''):
82
        url = self.get_url(relpath)
83
        if relpath:
84
            self.build_tree([relpath + '/'])
85
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
86
        repo = dir.create_repository()
87
        branch = dir.create_branch()
88
        try:
89
            return workingtree_4.WorkingTreeFormat4().initialize(dir)
90
        except errors.NotLocalUrl:
91
            raise TestSkipped('Not a local URL')
92
93
    def test_dirstate_stores_all_parent_inventories(self):
94
        tree = self.make_workingtree()
95
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
96
        # We're going to build in tree a working tree
97
        # with three parent trees, with some files in common.
98
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
99
        # We really don't want to do commit or merge in the new dirstate-based
100
        # tree, because that might not work yet.  So instead we build
101
        # revisions elsewhere and pull them across, doing by hand part of the
102
        # work that merge would do.
103
104
        subtree = self.make_branch_and_tree('subdir')
105
        # writelock the tree so its repository doesn't get readlocked by
106
        # the revision tree locks. This works around the bug where we dont
107
        # permit lock upgrading.
108
        subtree.lock_write()
109
        self.addCleanup(subtree.unlock)
110
        self.build_tree(['subdir/file-a',])
111
        subtree.add(['file-a'], ['id-a'])
112
        rev1 = subtree.commit('commit in subdir')
113
114
        subtree2 = subtree.bzrdir.sprout('subdir2').open_workingtree()
115
        self.build_tree(['subdir2/file-b'])
116
        subtree2.add(['file-b'], ['id-b'])
117
        rev2 = subtree2.commit('commit in subdir2')
118
119
        subtree.flush()
3462.1.7 by John Arbash Meinel
fix a test that assumed WT4.set_parent_trees() wouldn't filter the list.
120
        subtree3 = subtree.bzrdir.sprout('subdir3').open_workingtree()
121
        rev3 = subtree3.commit('merge from subdir2')
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
122
123
        repo = tree.branch.repository
3462.1.7 by John Arbash Meinel
fix a test that assumed WT4.set_parent_trees() wouldn't filter the list.
124
        repo.fetch(subtree.branch.repository, rev1)
125
        repo.fetch(subtree2.branch.repository, rev2)
126
        repo.fetch(subtree3.branch.repository, rev3)
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
127
        # will also pull the others...
128
129
        # create repository based revision trees
3462.1.7 by John Arbash Meinel
fix a test that assumed WT4.set_parent_trees() wouldn't filter the list.
130
        rev1_revtree = repo.revision_tree(rev1)
131
        rev2_revtree = repo.revision_tree(rev2)
132
        rev3_revtree = repo.revision_tree(rev3)
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
133
        # tree doesn't contain a text merge yet but we'll just
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
134
        # set the parents as if a merge had taken place.
135
        # this should cause the tree data to be folded into the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
136
        # dirstate.
137
        tree.set_parent_trees([
138
            (rev1, rev1_revtree),
139
            (rev2, rev2_revtree),
140
            (rev3, rev3_revtree), ])
141
142
        # create tree-sourced revision trees
143
        rev1_tree = tree.revision_tree(rev1)
144
        rev1_tree.lock_read()
145
        self.addCleanup(rev1_tree.unlock)
146
        rev2_tree = tree.revision_tree(rev2)
147
        rev2_tree.lock_read()
148
        self.addCleanup(rev2_tree.unlock)
149
        rev3_tree = tree.revision_tree(rev3)
150
        rev3_tree.lock_read()
151
        self.addCleanup(rev3_tree.unlock)
152
153
        # now we should be able to get them back out
154
        self.assertTreesEqual(rev1_revtree, rev1_tree)
155
        self.assertTreesEqual(rev2_revtree, rev2_tree)
156
        self.assertTreesEqual(rev3_revtree, rev3_tree)
157
158
    def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
159
        """Setting parent trees on a dirstate working tree takes
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
160
        the trees it's given and doesn't need to read them from the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
161
        repository.
162
        """
163
        tree = self.make_workingtree()
164
165
        subtree = self.make_branch_and_tree('subdir')
166
        rev1 = subtree.commit('commit in subdir')
167
        rev1_tree = subtree.basis_tree()
168
        rev1_tree.lock_read()
169
        self.addCleanup(rev1_tree.unlock)
170
171
        tree.branch.pull(subtree.branch)
172
173
        # break the repository's legs to make sure it only uses the trees
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
174
        # it's given; any calls to forbidden methods will raise an
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
175
        # AssertionError
176
        repo = tree.branch.repository
177
        repo.get_revision = self.fail
178
        repo.get_inventory = self.fail
4988.5.1 by Jelmer Vernooij
Rename Repository.get_inventory_xml -> Repository._get_inventory_xml.
179
        repo._get_inventory_xml = self.fail
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
180
        # try to set the parent trees.
181
        tree.set_parent_trees([(rev1, rev1_tree)])
182
183
    def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
184
        """Getting parent trees from a dirstate tree does not read from the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
185
        repos inventory store. This is an important part of the dirstate
186
        performance optimisation work.
187
        """
188
        tree = self.make_workingtree()
189
190
        subtree = self.make_branch_and_tree('subdir')
191
        # writelock the tree so its repository doesn't get readlocked by
192
        # the revision tree locks. This works around the bug where we dont
193
        # permit lock upgrading.
194
        subtree.lock_write()
195
        self.addCleanup(subtree.unlock)
196
        rev1 = subtree.commit('commit in subdir')
197
        rev1_tree = subtree.basis_tree()
198
        rev1_tree.lock_read()
199
        rev1_tree.inventory
200
        self.addCleanup(rev1_tree.unlock)
201
        rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
202
        rev2_tree = subtree.basis_tree()
203
        rev2_tree.lock_read()
204
        rev2_tree.inventory
205
        self.addCleanup(rev2_tree.unlock)
206
207
        tree.branch.pull(subtree.branch)
208
209
        # break the repository's legs to make sure it only uses the trees
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
210
        # it's given; any calls to forbidden methods will raise an
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
211
        # AssertionError
212
        repo = tree.branch.repository
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
213
        # dont uncomment this: the revision object must be accessed to
214
        # answer 'get_parent_ids' for the revision tree- dirstate does not
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
215
        # cache the parents of a parent tree at this point.
216
        #repo.get_revision = self.fail
217
        repo.get_inventory = self.fail
4988.5.1 by Jelmer Vernooij
Rename Repository.get_inventory_xml -> Repository._get_inventory_xml.
218
        repo._get_inventory_xml = self.fail
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
219
        # set the parent trees.
220
        tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
221
        # read the first tree
222
        result_rev1_tree = tree.revision_tree(rev1)
223
        # read the second
224
        result_rev2_tree = tree.revision_tree(rev2)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
225
        # compare - there should be no differences between the handed and
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
226
        # returned trees
227
        self.assertTreesEqual(rev1_tree, result_rev1_tree)
228
        self.assertTreesEqual(rev2_tree, result_rev2_tree)
229
230
    def test_dirstate_doesnt_cache_non_parent_trees(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
231
        """Getting parent trees from a dirstate tree does not read from the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
232
        repos inventory store. This is an important part of the dirstate
233
        performance optimisation work.
234
        """
235
        tree = self.make_workingtree()
236
237
        # make a tree that we can try for, which is able to be returned but
238
        # must not be
239
        subtree = self.make_branch_and_tree('subdir')
240
        rev1 = subtree.commit('commit in subdir')
241
        tree.branch.pull(subtree.branch)
242
        # check it fails
243
        self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
244
245
    def test_no_dirstate_outside_lock(self):
246
        # temporary test until the code is mature enough to test from outside.
247
        """Getting a dirstate object fails if there is no lock."""
248
        def lock_and_call_current_dirstate(tree, lock_method):
249
            getattr(tree, lock_method)()
250
            tree.current_dirstate()
251
            tree.unlock()
252
        tree = self.make_workingtree()
253
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
254
        lock_and_call_current_dirstate(tree, 'lock_read')
255
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
256
        lock_and_call_current_dirstate(tree, 'lock_write')
257
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
258
        lock_and_call_current_dirstate(tree, 'lock_tree_write')
259
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
260
5847.4.1 by John Arbash Meinel
When WT4.set_parent_trees() is called, sometimes we can use
261
    def test_set_parent_trees_uses_update_basis_by_delta(self):
262
        builder = self.make_branch_builder('source')
263
        builder.start_series()
264
        self.addCleanup(builder.finish_series)
265
        builder.build_snapshot('A', [], [
266
            ('add', ('', 'root-id', 'directory', None)),
267
            ('add', ('a', 'a-id', 'file', 'content\n'))])
268
        builder.build_snapshot('B', ['A'], [
269
            ('modify', ('a-id', 'new content\nfor a\n')),
270
            ('add', ('b', 'b-id', 'file', 'b-content\n'))])
271
        tree = self.make_workingtree('tree')
272
        source_branch = builder.get_branch()
273
        tree.branch.repository.fetch(source_branch.repository, 'B')
274
        tree.pull(source_branch, stop_revision='A')
275
        tree.lock_write()
276
        self.addCleanup(tree.unlock)
277
        state = tree.current_dirstate()
278
        called = []
279
        orig_update = state.update_basis_by_delta
280
        def log_update_basis_by_delta(delta, new_revid):
281
            called.append(new_revid)
282
            return orig_update(delta, new_revid)
283
        state.update_basis_by_delta = log_update_basis_by_delta
284
        basis = tree.basis_tree()
285
        self.assertEqual('a-id', basis.path2id('a'))
286
        self.assertEqual(None, basis.path2id('b'))
287
        def fail_set_parent_trees(trees, ghosts):
288
            raise AssertionError('dirstate.set_parent_trees() was called')
289
        state.set_parent_trees = fail_set_parent_trees
290
        repo = tree.branch.repository
291
        tree.pull(source_branch, stop_revision='B')
292
        self.assertEqual(['B'], called)
293
        basis = tree.basis_tree()
294
        self.assertEqual('a-id', basis.path2id('a'))
295
        self.assertEqual('b-id', basis.path2id('b'))
296
5847.4.10 by John Arbash Meinel
A few more bug fixes.
297
    def test_set_parent_trees_handles_missing_basis(self):
298
        builder = self.make_branch_builder('source')
299
        builder.start_series()
300
        self.addCleanup(builder.finish_series)
301
        builder.build_snapshot('A', [], [
302
            ('add', ('', 'root-id', 'directory', None)),
303
            ('add', ('a', 'a-id', 'file', 'content\n'))])
304
        builder.build_snapshot('B', ['A'], [
305
            ('modify', ('a-id', 'new content\nfor a\n')),
306
            ('add', ('b', 'b-id', 'file', 'b-content\n'))])
307
        builder.build_snapshot('C', ['A'], [
308
            ('add', ('c', 'c-id', 'file', 'c-content\n'))])
309
        b_c = self.make_branch('branch_with_c')
310
        b_c.pull(builder.get_branch(), stop_revision='C')
311
        b_b = self.make_branch('branch_with_b')
312
        b_b.pull(builder.get_branch(), stop_revision='B')
313
        # This is reproducing some of what 'switch' does, just to isolate the
314
        # set_parent_trees() step.
315
        wt = b_b.create_checkout('tree', lightweight=True)
316
        fmt = wt.bzrdir.find_branch_format()
317
        fmt.set_reference(wt.bzrdir, None, b_c)
318
        # Re-open with the new reference
319
        wt = wt.bzrdir.open_workingtree()
320
        wt.set_parent_trees([('C', b_c.repository.revision_tree('C'))])
321
        self.assertEqual(None, wt.basis_tree().path2id('b'))
322
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
323
    def test_new_dirstate_on_new_lock(self):
324
        # until we have detection for when a dirstate can be reused, we
325
        # want to reparse dirstate on every new lock.
326
        known_dirstates = set()
327
        def lock_and_compare_all_current_dirstate(tree, lock_method):
328
            getattr(tree, lock_method)()
329
            state = tree.current_dirstate()
330
            self.assertFalse(state in known_dirstates)
331
            known_dirstates.add(state)
332
            tree.unlock()
333
        tree = self.make_workingtree()
334
        # lock twice with each type to prevent silly per-lock-type bugs.
335
        # each lock and compare looks for a unique state object.
336
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
337
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
338
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
339
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
340
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
341
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
342
2255.2.122 by Robert Collins
Alter intertree implementation tests to let dirstate inter-trees be correctly parameterised.
343
    def test_constructing_invalid_interdirstate_raises(self):
344
        tree = self.make_workingtree()
345
        rev_id = tree.commit('first post')
346
        rev_id2 = tree.commit('second post')
347
        rev_tree = tree.branch.repository.revision_tree(rev_id)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
348
        # Exception is not a great thing to raise, but this test is
349
        # very short, and code is used to sanity check other tests, so
2255.2.122 by Robert Collins
Alter intertree implementation tests to let dirstate inter-trees be correctly parameterised.
350
        # a full error object is YAGNI.
351
        self.assertRaises(
352
            Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
353
        self.assertRaises(
354
            Exception, workingtree_4.InterDirStateTree, tree, rev_tree)
355
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
356
    def test_revtree_to_revtree_not_interdirstate(self):
357
        # we should not get a dirstate optimiser for two repository sourced
358
        # revtrees. we can't prove a negative, so we dont do exhaustive tests
359
        # of all formats; though that could be written in the future it doesn't
360
        # seem well worth it.
361
        tree = self.make_workingtree()
362
        rev_id = tree.commit('first post')
363
        rev_id2 = tree.commit('second post')
364
        rev_tree = tree.branch.repository.revision_tree(rev_id)
365
        rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
366
        optimiser = InterTree.get(rev_tree, rev_tree2)
367
        self.assertIsInstance(optimiser, InterTree)
368
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
369
        optimiser = InterTree.get(rev_tree2, rev_tree)
370
        self.assertIsInstance(optimiser, InterTree)
371
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
372
373
    def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
374
        # we should not get a dirstate optimiser when the revision id for of
375
        # the source is not in the dirstate of the target.
376
        tree = self.make_workingtree()
377
        rev_id = tree.commit('first post')
378
        rev_id2 = tree.commit('second post')
379
        rev_tree = tree.branch.repository.revision_tree(rev_id)
380
        tree.lock_read()
381
        optimiser = InterTree.get(rev_tree, tree)
382
        self.assertIsInstance(optimiser, InterTree)
383
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
384
        optimiser = InterTree.get(tree, rev_tree)
385
        self.assertIsInstance(optimiser, InterTree)
386
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
387
        tree.unlock()
388
389
    def test_empty_basis_to_dirstate_tree(self):
390
        # we should get a InterDirStateTree for doing
391
        # 'changes_from' from the first basis dirstate revision tree to a
392
        # WorkingTree4.
393
        tree = self.make_workingtree()
394
        tree.lock_read()
395
        basis_tree = tree.basis_tree()
396
        basis_tree.lock_read()
397
        optimiser = InterTree.get(basis_tree, tree)
398
        tree.unlock()
399
        basis_tree.unlock()
400
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
401
402
    def test_nonempty_basis_to_dirstate_tree(self):
403
        # we should get a InterDirStateTree for doing
404
        # 'changes_from' from a non-null basis dirstate revision tree to a
405
        # WorkingTree4.
406
        tree = self.make_workingtree()
407
        tree.commit('first post')
408
        tree.lock_read()
409
        basis_tree = tree.basis_tree()
410
        basis_tree.lock_read()
411
        optimiser = InterTree.get(basis_tree, tree)
412
        tree.unlock()
413
        basis_tree.unlock()
414
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
415
416
    def test_empty_basis_revtree_to_dirstate_tree(self):
417
        # we should get a InterDirStateTree for doing
418
        # 'changes_from' from an empty repository based rev tree to a
419
        # WorkingTree4.
420
        tree = self.make_workingtree()
421
        tree.lock_read()
422
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
423
        basis_tree.lock_read()
424
        optimiser = InterTree.get(basis_tree, tree)
425
        tree.unlock()
426
        basis_tree.unlock()
427
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
428
429
    def test_nonempty_basis_revtree_to_dirstate_tree(self):
430
        # we should get a InterDirStateTree for doing
431
        # 'changes_from' from a non-null repository based rev tree to a
432
        # WorkingTree4.
433
        tree = self.make_workingtree()
434
        tree.commit('first post')
435
        tree.lock_read()
436
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
437
        basis_tree.lock_read()
438
        optimiser = InterTree.get(basis_tree, tree)
439
        tree.unlock()
440
        basis_tree.unlock()
441
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
442
443
    def test_tree_to_basis_in_other_tree(self):
444
        # we should get a InterDirStateTree when
445
        # the source revid is in the dirstate object of the target and
446
        # the dirstates are different. This is largely covered by testing
447
        # with repository revtrees, so is just for extra confidence.
448
        tree = self.make_workingtree('a')
449
        tree.commit('first post')
450
        tree2 = self.make_workingtree('b')
451
        tree2.pull(tree.branch)
452
        basis_tree = tree.basis_tree()
453
        tree2.lock_read()
454
        basis_tree.lock_read()
455
        optimiser = InterTree.get(basis_tree, tree2)
456
        tree2.unlock()
457
        basis_tree.unlock()
458
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
459
460
    def test_merged_revtree_to_tree(self):
461
        # we should get a InterDirStateTree when
462
        # the source tree is a merged tree present in the dirstate of target.
463
        tree = self.make_workingtree('a')
464
        tree.commit('first post')
465
        tree.commit('tree 1 commit 2')
466
        tree2 = self.make_workingtree('b')
467
        tree2.pull(tree.branch)
468
        tree2.commit('tree 2 commit 2')
469
        tree.merge_from_branch(tree2.branch)
470
        second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
471
        second_parent_tree.lock_read()
472
        tree.lock_read()
473
        optimiser = InterTree.get(second_parent_tree, tree)
474
        tree.unlock()
475
        second_parent_tree.unlock()
476
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
477
478
    def test_id2path(self):
479
        tree = self.make_workingtree('tree')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
480
        self.build_tree(['tree/a', 'tree/b'])
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
481
        tree.add(['a'], ['a-id'])
482
        self.assertEqual(u'a', tree.id2path('a-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
483
        self.assertRaises(errors.NoSuchId, tree.id2path, 'a')
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
484
        tree.commit('a')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
485
        tree.add(['b'], ['b-id'])
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
486
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
487
        try:
2825.6.1 by Robert Collins
* ``WorkingTree.rename_one`` will now raise an error if normalisation of the
488
            new_path = u'b\u03bcrry'
489
            tree.rename_one('a', new_path)
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
490
        except UnicodeEncodeError:
491
            # support running the test on non-unicode platforms
492
            new_path = 'c'
2825.6.1 by Robert Collins
* ``WorkingTree.rename_one`` will now raise an error if normalisation of the
493
            tree.rename_one('a', new_path)
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
494
        self.assertEqual(new_path, tree.id2path('a-id'))
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
495
        tree.commit(u'b\xb5rry')
496
        tree.unversion(['a-id'])
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
497
        self.assertRaises(errors.NoSuchId, tree.id2path, 'a-id')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
498
        self.assertEqual('b', tree.id2path('b-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
499
        self.assertRaises(errors.NoSuchId, tree.id2path, 'c-id')
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
500
501
    def test_unique_root_id_per_tree(self):
502
        # each time you initialize a new tree, it gets a different root id
2255.2.207 by Robert Collins
Reinstate format change for test_workingtree_4
503
        format_name = 'dirstate-with-subtree'
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
504
        tree1 = self.make_branch_and_tree('tree1',
505
            format=format_name)
506
        tree2 = self.make_branch_and_tree('tree2',
507
            format=format_name)
508
        self.assertNotEqual(tree1.get_root_id(), tree2.get_root_id())
509
        # when you branch, it inherits the same root id
510
        rev1 = tree1.commit('first post')
511
        tree3 = tree1.bzrdir.sprout('tree3').open_workingtree()
512
        self.assertEqual(tree3.get_root_id(), tree1.get_root_id())
513
2255.11.2 by Martin Pool
Add more dirstate root-id-changing tests
514
    def test_set_root_id(self):
515
        # similar to some code that fails in the dirstate-plus-subtree branch
516
        # -- setting the root id while adding a parent seems to scramble the
517
        # dirstate invariants. -- mbp 20070303
518
        def validate():
519
            wt.lock_read()
520
            try:
521
                wt.current_dirstate()._validate()
522
            finally:
523
                wt.unlock()
524
        wt = self.make_workingtree('tree')
525
        wt.set_root_id('TREE-ROOTID')
526
        validate()
527
        wt.commit('somenthing')
528
        validate()
529
        # now switch and commit again
530
        wt.set_root_id('tree-rootid')
531
        validate()
532
        wt.commit('again')
533
        validate()
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
534
1551.15.6 by Aaron Bentley
Use ROOT_ID when the repository supports old clients (Bug #107168)
535
    def test_default_root_id(self):
536
        tree = self.make_branch_and_tree('tag', format='dirstate-tags')
537
        self.assertEqual(inventory.ROOT_ID, tree.get_root_id())
538
        tree = self.make_branch_and_tree('subtree',
539
                                         format='dirstate-with-subtree')
540
        self.assertNotEqual(inventory.ROOT_ID, tree.get_root_id())
541
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
542
    def test_non_subtree_with_nested_trees(self):
543
        # prior to dirstate, st/diff/commit ignored nested trees.
544
        # dirstate, as opposed to dirstate-with-subtree, should
545
        # behave the same way.
546
        tree = self.make_branch_and_tree('.', format='dirstate')
547
        self.assertFalse(tree.supports_tree_reference())
548
        self.build_tree(['dir/'])
549
        # for testing easily.
550
        tree.set_root_id('root')
551
        tree.add(['dir'], ['dir-id'])
552
        subtree = self.make_branch_and_tree('dir')
553
        # the most primitive operation: kind
554
        self.assertEqual('directory', tree.kind('dir-id'))
4570.2.8 by Robert Collins
Adjust WorkingTree4 specific test to deal with iter_changes reporting required directories.
555
        # a diff against the basis should give us a directory and the root (as
556
        # the root is new too).
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
557
        tree.lock_read()
558
        expected = [('dir-id',
559
            (None, u'dir'),
560
            True,
561
            (False, True),
562
            (None, 'root'),
563
            (None, u'dir'),
564
            (None, 'directory'),
4570.2.8 by Robert Collins
Adjust WorkingTree4 specific test to deal with iter_changes reporting required directories.
565
            (None, False)),
566
            ('root', (None, u''), True, (False, True), (None, None),
567
            (None, u''), (None, 'directory'), (None, 0))]
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
568
        self.assertEqual(expected, list(tree.iter_changes(tree.basis_tree(),
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
569
            specific_files=['dir'])))
570
        tree.unlock()
571
        # do a commit, we want to trigger the dirstate fast-path too
572
        tree.commit('first post')
573
        # change the path for the subdir, which will trigger getting all
574
        # its data:
575
        os.rename('dir', 'also-dir')
576
        # now the diff will use the fast path
577
        tree.lock_read()
578
        expected = [('dir-id',
579
            (u'dir', u'dir'),
580
            True,
581
            (True, True),
582
            ('root', 'root'),
583
            ('dir', 'dir'),
584
            ('directory', None),
585
            (False, False))]
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
586
        self.assertEqual(expected, list(tree.iter_changes(tree.basis_tree())))
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
587
        tree.unlock()
588
589
    def test_with_subtree_supports_tree_references(self):
590
        # dirstate-with-subtree should support tree-references.
591
        tree = self.make_branch_and_tree('.', format='dirstate-with-subtree')
592
        self.assertTrue(tree.supports_tree_reference())
593
        # having checked this is on, the tree interface, and intertree
594
        # interface tests, will proceed to test the subtree support of
595
        # workingtree_4.
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
596
597
    def test_iter_changes_ignores_unversioned_dirs(self):
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
598
        """iter_changes should not descend into unversioned directories."""
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
599
        tree = self.make_branch_and_tree('.', format='dirstate')
600
        # We have an unversioned directory at the root, a versioned one with
601
        # other versioned files and an unversioned directory, and another
602
        # versioned dir with nothing but an unversioned directory.
603
        self.build_tree(['unversioned/',
604
                         'unversioned/a',
605
                         'unversioned/b/',
606
                         'versioned/',
607
                         'versioned/unversioned/',
608
                         'versioned/unversioned/a',
609
                         'versioned/unversioned/b/',
610
                         'versioned2/',
611
                         'versioned2/a',
612
                         'versioned2/unversioned/',
613
                         'versioned2/unversioned/a',
614
                         'versioned2/unversioned/b/',
615
                        ])
616
        tree.add(['versioned', 'versioned2', 'versioned2/a'])
617
        tree.commit('one', rev_id='rev-1')
618
        # Trap osutils._walkdirs_utf8 to spy on what dirs have been accessed.
619
        returned = []
620
        def walkdirs_spy(*args, **kwargs):
4985.1.5 by Vincent Ladeuil
Deploying the new overrideAttr facility further reduces the complexity
621
            for val in orig(*args, **kwargs):
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
622
                returned.append(val[0][0])
623
                yield val
4985.1.5 by Vincent Ladeuil
Deploying the new overrideAttr facility further reduces the complexity
624
        orig = self.overrideAttr(osutils, '_walkdirs_utf8', walkdirs_spy)
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
625
626
        basis = tree.basis_tree()
627
        tree.lock_read()
628
        self.addCleanup(tree.unlock)
629
        basis.lock_read()
630
        self.addCleanup(basis.unlock)
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
631
        changes = [c[1] for c in
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
632
                   tree.iter_changes(basis, want_unversioned=True)]
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
633
        self.assertEqual([(None, 'unversioned'),
634
                          (None, 'versioned/unversioned'),
635
                          (None, 'versioned2/unversioned'),
636
                         ], changes)
637
        self.assertEqual(['', 'versioned', 'versioned2'], returned)
638
        del returned[:] # reset
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
639
        changes = [c[1] for c in tree.iter_changes(basis)]
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
640
        self.assertEqual([], changes)
641
        self.assertEqual(['', 'versioned', 'versioned2'], returned)
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
642
4496.2.1 by Ian Clatworthy
(igc) Improve paths are not versioned reporting (Benoît PIERRE)
643
    def test_iter_changes_unversioned_error(self):
644
        """ Check if a PathsNotVersionedError is correctly raised and the
645
            paths list contains all unversioned entries only.
646
        """
647
        tree = self.make_branch_and_tree('tree')
648
        self.build_tree_contents([('tree/bar', '')])
649
        tree.add(['bar'], ['bar-id'])
650
        tree.lock_read()
651
        self.addCleanup(tree.unlock)
652
        tree_iter_changes = lambda files: [
653
            c for c in tree.iter_changes(tree.basis_tree(), specific_files=files,
654
                                         require_versioned=True)
655
        ]
656
        e = self.assertRaises(errors.PathsNotVersionedError,
657
                              tree_iter_changes, ['bar', 'foo'])
658
        self.assertEqual(e.paths, ['foo'])
659
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
660
    def get_tree_with_cachable_file_foo(self):
661
        tree = self.make_branch_and_tree('.')
5755.1.1 by John Arbash Meinel
Change WT._observed_sha1 to also update st.st_size.
662
        tree.lock_write()
663
        self.addCleanup(tree.unlock)
664
        self.build_tree_contents([('foo', 'a bit of content for foo\n')])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
665
        tree.add(['foo'], ['foo-id'])
5755.1.1 by John Arbash Meinel
Change WT._observed_sha1 to also update st.st_size.
666
        tree.current_dirstate()._cutoff_time = time.time() + 60
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
667
        return tree
668
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
669
    def test_commit_updates_hash_cache(self):
670
        tree = self.get_tree_with_cachable_file_foo()
671
        revid = tree.commit('a commit')
672
        # tree's dirstate should now have a valid stat entry for foo.
673
        entry = tree._get_entry(path='foo')
674
        expected_sha1 = osutils.sha_file_by_name('foo')
675
        self.assertEqual(expected_sha1, entry[1][0][1])
5755.1.2 by John Arbash Meinel
use soft constants rather than '25'
676
        self.assertEqual(len('a bit of content for foo\n'), entry[1][0][2])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
677
678
    def test_observed_sha1_cachable(self):
679
        tree = self.get_tree_with_cachable_file_foo()
680
        expected_sha1 = osutils.sha_file_by_name('foo')
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
681
        statvalue = os.lstat("foo")
5755.1.1 by John Arbash Meinel
Change WT._observed_sha1 to also update st.st_size.
682
        tree._observed_sha1("foo-id", "foo", (expected_sha1, statvalue))
683
        entry = tree._get_entry(path="foo")
684
        entry_state = entry[1][0]
685
        self.assertEqual(expected_sha1, entry_state[1])
5755.1.2 by John Arbash Meinel
use soft constants rather than '25'
686
        self.assertEqual(statvalue.st_size, entry_state[2])
5755.1.1 by John Arbash Meinel
Change WT._observed_sha1 to also update st.st_size.
687
        tree.unlock()
688
        tree.lock_read()
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
689
        tree = tree.bzrdir.open_workingtree()
690
        tree.lock_read()
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
691
        self.addCleanup(tree.unlock)
5755.1.1 by John Arbash Meinel
Change WT._observed_sha1 to also update st.st_size.
692
        entry = tree._get_entry(path="foo")
693
        entry_state = entry[1][0]
694
        self.assertEqual(expected_sha1, entry_state[1])
5755.1.2 by John Arbash Meinel
use soft constants rather than '25'
695
        self.assertEqual(statvalue.st_size, entry_state[2])
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
696
697
    def test_observed_sha1_new_file(self):
698
        tree = self.make_branch_and_tree('.')
699
        self.build_tree(['foo'])
700
        tree.add(['foo'], ['foo-id'])
3207.2.2 by John Arbash Meinel
Fix bug #187169, when an invalid delta is supplied to update_basis_by_delta
701
        tree.lock_read()
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
702
        try:
703
            current_sha1 = tree._get_entry(path="foo")[1][0][1]
704
        finally:
705
            tree.unlock()
706
        tree.lock_write()
707
        try:
708
            tree._observed_sha1("foo-id", "foo",
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
709
                (osutils.sha_file_by_name('foo'), os.lstat("foo")))
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
710
            # Must not have changed
711
            self.assertEqual(current_sha1,
712
                tree._get_entry(path="foo")[1][0][1])
713
        finally:
714
            tree.unlock()
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
715
716
    def test_get_file_with_stat_id_only(self):
717
        # Explicit test to ensure we get a lstat value from WT4 trees.
718
        tree = self.make_branch_and_tree('.')
719
        self.build_tree(['foo'])
720
        tree.add(['foo'], ['foo-id'])
721
        tree.lock_read()
722
        self.addCleanup(tree.unlock)
723
        file_obj, statvalue = tree.get_file_with_stat('foo-id')
724
        expected = os.lstat('foo')
4807.2.2 by John Arbash Meinel
Move all the stat comparison and platform checkning code to assertEqualStat.
725
        self.assertEqualStat(expected, statvalue)
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
726
        self.assertEqual(["contents of foo\n"], file_obj.readlines())
727
728
729
class TestCorruptDirstate(TestCaseWithTransport):
730
    """Tests for how we handle when the dirstate has been corrupted."""
731
732
    def create_wt4(self):
733
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
734
        control.create_repository()
735
        control.create_branch()
736
        tree = workingtree_4.WorkingTreeFormat4().initialize(control)
737
        return tree
738
739
    def test_invalid_rename(self):
740
        tree = self.create_wt4()
741
        # Create a corrupted dirstate
742
        tree.lock_write()
743
        try:
4285.2.1 by Vincent Ladeuil
Cleanup test imports and use features to better track skipped tests.
744
            # We need a parent, or we always compare with NULL
745
            tree.commit('init')
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
746
            state = tree.current_dirstate()
747
            state._read_dirblocks_if_needed()
748
            # Now add in an invalid entry, a rename with a dangling pointer
749
            state._dirblocks[1][1].append((('', 'foo', 'foo-id'),
750
                                            [('f', '', 0, False, ''),
751
                                             ('r', 'bar', 0 , False, '')]))
752
            self.assertListRaises(errors.CorruptDirstate,
753
                                  tree.iter_changes, tree.basis_tree())
754
        finally:
755
            tree.unlock()
756
757
    def get_simple_dirblocks(self, state):
758
        """Extract the simple information from the DirState.
759
760
        This returns the dirblocks, only with the sha1sum and stat details
761
        filtered out.
762
        """
763
        simple_blocks = []
764
        for block in state._dirblocks:
765
            simple_block = (block[0], [])
766
            for entry in block[1]:
767
                # Include the key for each entry, and for each parent include
768
                # just the minikind, so we know if it was
769
                # present/absent/renamed/etc
770
                simple_block[1].append((entry[0], [i[0] for i in entry[1]]))
771
            simple_blocks.append(simple_block)
772
        return simple_blocks
773
774
    def test_update_basis_with_invalid_delta(self):
775
        """When given an invalid delta, it should abort, and not be saved."""
776
        self.build_tree(['dir/', 'dir/file'])
777
        tree = self.create_wt4()
778
        tree.lock_write()
779
        self.addCleanup(tree.unlock)
780
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
781
        first_revision_id = tree.commit('init')
782
783
        root_id = tree.path2id('')
784
        state = tree.current_dirstate()
785
        state._read_dirblocks_if_needed()
786
        self.assertEqual([
787
            ('', [(('', '', root_id), ['d', 'd'])]),
788
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
789
            ('dir', [(('dir', 'file', 'file-id'), ['f', 'f'])]),
790
        ],  self.get_simple_dirblocks(state))
791
792
        tree.remove(['dir/file'])
793
        self.assertEqual([
794
            ('', [(('', '', root_id), ['d', 'd'])]),
795
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
796
            ('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
797
        ],  self.get_simple_dirblocks(state))
798
        # Make sure the removal is written to disk
799
        tree.flush()
800
801
        # self.assertRaises(Exception, tree.update_basis_by_delta,
802
        new_dir = inventory.InventoryDirectory('dir-id', 'new-dir', root_id)
803
        new_dir.revision = 'new-revision-id'
804
        new_file = inventory.InventoryFile('file-id', 'new-file', root_id)
805
        new_file.revision = 'new-revision-id'
806
        self.assertRaises(errors.InconsistentDelta,
807
            tree.update_basis_by_delta, 'new-revision-id',
808
            [('dir', 'new-dir', 'dir-id', new_dir),
809
             ('dir/file', 'new-dir/new-file', 'file-id', new_file),
810
            ])
811
        del state
812
813
        # Now when we re-read the file it should not have been modified
814
        tree.unlock()
815
        tree.lock_read()
816
        self.assertEqual(first_revision_id, tree.last_revision())
817
        state = tree.current_dirstate()
818
        state._read_dirblocks_if_needed()
819
        self.assertEqual([
820
            ('', [(('', '', root_id), ['d', 'd'])]),
821
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
822
            ('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
823
        ],  self.get_simple_dirblocks(state))
4634.156.1 by Vincent Ladeuil
Don't traceback when unversioning a directory.
824
825
826
class TestInventoryCoherency(TestCaseWithTransport):
827
828
    def test_inventory_is_synced_when_unversioning_a_dir(self):
829
        """Unversioning the root of a subtree unversions the entire subtree."""
830
        tree = self.make_branch_and_tree('.')
831
        self.build_tree(['a/', 'a/b', 'c/'])
832
        tree.add(['a', 'a/b', 'c'], ['a-id', 'b-id', 'c-id'])
833
        # within a lock unversion should take effect
834
        tree.lock_write()
835
        self.addCleanup(tree.unlock)
836
        # Force access to the in memory inventory to trigger bug #494221: try
837
        # maintaining the in-memory inventory
838
        inv = tree.inventory
4634.156.2 by Vincent Ladeuil
Ensure the entries are removed from the inventory
839
        self.assertTrue(inv.has_id('a-id'))
840
        self.assertTrue(inv.has_id('b-id'))
4634.156.1 by Vincent Ladeuil
Don't traceback when unversioning a directory.
841
        tree.unversion(['a-id', 'b-id'])
4634.156.2 by Vincent Ladeuil
Ensure the entries are removed from the inventory
842
        self.assertFalse(inv.has_id('a-id'))
843
        self.assertFalse(inv.has_id('b-id'))