~bzr-pqm/bzr/bzr.dev

2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
1
# Copyright (C) 2005, 2006 Canonical Ltd
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
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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
21
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
22
from bzrlib import (
23
    bzrdir,
24
    dirstate,
25
    errors,
1551.15.6 by Aaron Bentley
Use ROOT_ID when the repository supports old clients (Bug #107168)
26
    inventory,
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
27
    osutils,
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
28
    workingtree_4,
29
    )
30
from bzrlib.lockdir import LockDir
31
from bzrlib.tests import TestCaseWithTransport, TestSkipped
32
from bzrlib.tree import InterTree
33
34
35
class TestWorkingTreeFormat4(TestCaseWithTransport):
36
    """Tests specific to WorkingTreeFormat4."""
37
38
    def test_disk_layout(self):
39
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
40
        control.create_repository()
41
        control.create_branch()
42
        tree = workingtree_4.WorkingTreeFormat4().initialize(control)
43
        # we want:
44
        # format 'Bazaar Working Tree format 4'
45
        # stat-cache = ??
46
        t = control.get_workingtree_transport(None)
2255.2.230 by Robert Collins
Update tree format signatures to mention introducing bzr version.
47
        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
48
                             t.get('format').read())
49
        self.assertFalse(t.has('inventory.basis'))
50
        # no last-revision file means 'None' or 'NULLREVISION'
51
        self.assertFalse(t.has('last-revision'))
52
        state = dirstate.DirState.on_file(t.local_abspath('dirstate'))
53
        state.lock_read()
54
        try:
55
            self.assertEqual([], state.get_parent_ids())
56
        finally:
57
            state.unlock()
58
59
    def test_uses_lockdir(self):
60
        """WorkingTreeFormat4 uses its own LockDir:
2255.10.1 by John Arbash Meinel
Update WorkingTree4 so that it doesn't use a HashCache,
61
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
62
            - lock is a directory
63
            - when the WorkingTree is locked, LockDir can see that
64
        """
65
        # this test could be factored into a subclass of tests common to both
66
        # format 3 and 4, but for now its not much of an issue as there is only one in common.
67
        t = self.get_transport()
68
        tree = self.make_workingtree()
69
        self.assertIsDirectory('.bzr', t)
70
        self.assertIsDirectory('.bzr/checkout', t)
71
        self.assertIsDirectory('.bzr/checkout/lock', t)
72
        our_lock = LockDir(t, '.bzr/checkout/lock')
73
        self.assertEquals(our_lock.peek(), None)
74
        tree.lock_write()
75
        self.assertTrue(our_lock.peek())
76
        tree.unlock()
77
        self.assertEquals(our_lock.peek(), None)
78
79
    def make_workingtree(self, relpath=''):
80
        url = self.get_url(relpath)
81
        if relpath:
82
            self.build_tree([relpath + '/'])
83
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
84
        repo = dir.create_repository()
85
        branch = dir.create_branch()
86
        try:
87
            return workingtree_4.WorkingTreeFormat4().initialize(dir)
88
        except errors.NotLocalUrl:
89
            raise TestSkipped('Not a local URL')
90
91
    def test_dirstate_stores_all_parent_inventories(self):
92
        tree = self.make_workingtree()
93
94
        # We're going to build in tree a working tree 
95
        # with three parent trees, with some files in common.  
96
    
97
        # We really don't want to do commit or merge in the new dirstate-based
98
        # tree, because that might not work yet.  So instead we build
99
        # revisions elsewhere and pull them across, doing by hand part of the
100
        # work that merge would do.
101
102
        subtree = self.make_branch_and_tree('subdir')
103
        # writelock the tree so its repository doesn't get readlocked by
104
        # the revision tree locks. This works around the bug where we dont
105
        # permit lock upgrading.
106
        subtree.lock_write()
107
        self.addCleanup(subtree.unlock)
108
        self.build_tree(['subdir/file-a',])
109
        subtree.add(['file-a'], ['id-a'])
110
        rev1 = subtree.commit('commit in subdir')
111
112
        subtree2 = subtree.bzrdir.sprout('subdir2').open_workingtree()
113
        self.build_tree(['subdir2/file-b'])
114
        subtree2.add(['file-b'], ['id-b'])
115
        rev2 = subtree2.commit('commit in subdir2')
116
117
        subtree.flush()
118
        subtree.merge_from_branch(subtree2.branch)
119
        rev3 = subtree.commit('merge from subdir2')
120
121
        repo = tree.branch.repository
122
        repo.fetch(subtree.branch.repository, rev3)
123
        # will also pull the others...
124
125
        # create repository based revision trees
126
        rev1_revtree = subtree.branch.repository.revision_tree(rev1)
127
        rev2_revtree = subtree2.branch.repository.revision_tree(rev2)
128
        rev3_revtree = subtree.branch.repository.revision_tree(rev3)
129
        # tree doesn't contain a text merge yet but we'll just
130
        # set the parents as if a merge had taken place. 
131
        # this should cause the tree data to be folded into the 
132
        # dirstate.
133
        tree.set_parent_trees([
134
            (rev1, rev1_revtree),
135
            (rev2, rev2_revtree),
136
            (rev3, rev3_revtree), ])
137
138
        # create tree-sourced revision trees
139
        rev1_tree = tree.revision_tree(rev1)
140
        rev1_tree.lock_read()
141
        self.addCleanup(rev1_tree.unlock)
142
        rev2_tree = tree.revision_tree(rev2)
143
        rev2_tree.lock_read()
144
        self.addCleanup(rev2_tree.unlock)
145
        rev3_tree = tree.revision_tree(rev3)
146
        rev3_tree.lock_read()
147
        self.addCleanup(rev3_tree.unlock)
148
149
        # now we should be able to get them back out
150
        self.assertTreesEqual(rev1_revtree, rev1_tree)
151
        self.assertTreesEqual(rev2_revtree, rev2_tree)
152
        self.assertTreesEqual(rev3_revtree, rev3_tree)
153
154
    def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
155
        """Setting parent trees on a dirstate working tree takes
156
        the trees it's given and doesn't need to read them from the 
157
        repository.
158
        """
159
        tree = self.make_workingtree()
160
161
        subtree = self.make_branch_and_tree('subdir')
162
        rev1 = subtree.commit('commit in subdir')
163
        rev1_tree = subtree.basis_tree()
164
        rev1_tree.lock_read()
165
        self.addCleanup(rev1_tree.unlock)
166
167
        tree.branch.pull(subtree.branch)
168
169
        # break the repository's legs to make sure it only uses the trees
170
        # it's given; any calls to forbidden methods will raise an 
171
        # AssertionError
172
        repo = tree.branch.repository
173
        repo.get_revision = self.fail
174
        repo.get_inventory = self.fail
175
        repo.get_inventory_xml = self.fail
176
        # try to set the parent trees.
177
        tree.set_parent_trees([(rev1, rev1_tree)])
178
179
    def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
180
        """Getting parent trees from a dirstate tree does not read from the 
181
        repos inventory store. This is an important part of the dirstate
182
        performance optimisation work.
183
        """
184
        tree = self.make_workingtree()
185
186
        subtree = self.make_branch_and_tree('subdir')
187
        # writelock the tree so its repository doesn't get readlocked by
188
        # the revision tree locks. This works around the bug where we dont
189
        # permit lock upgrading.
190
        subtree.lock_write()
191
        self.addCleanup(subtree.unlock)
192
        rev1 = subtree.commit('commit in subdir')
193
        rev1_tree = subtree.basis_tree()
194
        rev1_tree.lock_read()
195
        rev1_tree.inventory
196
        self.addCleanup(rev1_tree.unlock)
197
        rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
198
        rev2_tree = subtree.basis_tree()
199
        rev2_tree.lock_read()
200
        rev2_tree.inventory
201
        self.addCleanup(rev2_tree.unlock)
202
203
        tree.branch.pull(subtree.branch)
204
205
        # break the repository's legs to make sure it only uses the trees
206
        # it's given; any calls to forbidden methods will raise an 
207
        # AssertionError
208
        repo = tree.branch.repository
209
        # dont uncomment this: the revision object must be accessed to 
210
        # answer 'get_parent_ids' for the revision tree- dirstate does not 
211
        # cache the parents of a parent tree at this point.
212
        #repo.get_revision = self.fail
213
        repo.get_inventory = self.fail
214
        repo.get_inventory_xml = self.fail
215
        # set the parent trees.
216
        tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
217
        # read the first tree
218
        result_rev1_tree = tree.revision_tree(rev1)
219
        # read the second
220
        result_rev2_tree = tree.revision_tree(rev2)
221
        # compare - there should be no differences between the handed and 
222
        # returned trees
223
        self.assertTreesEqual(rev1_tree, result_rev1_tree)
224
        self.assertTreesEqual(rev2_tree, result_rev2_tree)
225
226
    def test_dirstate_doesnt_cache_non_parent_trees(self):
227
        """Getting parent trees from a dirstate tree does not read from the 
228
        repos inventory store. This is an important part of the dirstate
229
        performance optimisation work.
230
        """
231
        tree = self.make_workingtree()
232
233
        # make a tree that we can try for, which is able to be returned but
234
        # must not be
235
        subtree = self.make_branch_and_tree('subdir')
236
        rev1 = subtree.commit('commit in subdir')
237
        tree.branch.pull(subtree.branch)
238
        # check it fails
239
        self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
240
241
    def test_no_dirstate_outside_lock(self):
242
        # temporary test until the code is mature enough to test from outside.
243
        """Getting a dirstate object fails if there is no lock."""
244
        def lock_and_call_current_dirstate(tree, lock_method):
245
            getattr(tree, lock_method)()
246
            tree.current_dirstate()
247
            tree.unlock()
248
        tree = self.make_workingtree()
249
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
250
        lock_and_call_current_dirstate(tree, 'lock_read')
251
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
252
        lock_and_call_current_dirstate(tree, 'lock_write')
253
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
254
        lock_and_call_current_dirstate(tree, 'lock_tree_write')
255
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
256
257
    def test_new_dirstate_on_new_lock(self):
258
        # until we have detection for when a dirstate can be reused, we
259
        # want to reparse dirstate on every new lock.
260
        known_dirstates = set()
261
        def lock_and_compare_all_current_dirstate(tree, lock_method):
262
            getattr(tree, lock_method)()
263
            state = tree.current_dirstate()
264
            self.assertFalse(state in known_dirstates)
265
            known_dirstates.add(state)
266
            tree.unlock()
267
        tree = self.make_workingtree()
268
        # lock twice with each type to prevent silly per-lock-type bugs.
269
        # each lock and compare looks for a unique state object.
270
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
271
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
272
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
273
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
274
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
275
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
276
2255.2.122 by Robert Collins
Alter intertree implementation tests to let dirstate inter-trees be correctly parameterised.
277
    def test_constructing_invalid_interdirstate_raises(self):
278
        tree = self.make_workingtree()
279
        rev_id = tree.commit('first post')
280
        rev_id2 = tree.commit('second post')
281
        rev_tree = tree.branch.repository.revision_tree(rev_id)
282
        # Exception is not a great thing to raise, but this test is 
283
        # very short, and code is used to sanity check other tests, so 
284
        # a full error object is YAGNI.
285
        self.assertRaises(
286
            Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
287
        self.assertRaises(
288
            Exception, workingtree_4.InterDirStateTree, tree, rev_tree)
289
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
290
    def test_revtree_to_revtree_not_interdirstate(self):
291
        # we should not get a dirstate optimiser for two repository sourced
292
        # revtrees. we can't prove a negative, so we dont do exhaustive tests
293
        # of all formats; though that could be written in the future it doesn't
294
        # seem well worth it.
295
        tree = self.make_workingtree()
296
        rev_id = tree.commit('first post')
297
        rev_id2 = tree.commit('second post')
298
        rev_tree = tree.branch.repository.revision_tree(rev_id)
299
        rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
300
        optimiser = InterTree.get(rev_tree, rev_tree2)
301
        self.assertIsInstance(optimiser, InterTree)
302
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
303
        optimiser = InterTree.get(rev_tree2, rev_tree)
304
        self.assertIsInstance(optimiser, InterTree)
305
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
306
307
    def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
308
        # we should not get a dirstate optimiser when the revision id for of
309
        # the source is not in the dirstate of the target.
310
        tree = self.make_workingtree()
311
        rev_id = tree.commit('first post')
312
        rev_id2 = tree.commit('second post')
313
        rev_tree = tree.branch.repository.revision_tree(rev_id)
314
        tree.lock_read()
315
        optimiser = InterTree.get(rev_tree, tree)
316
        self.assertIsInstance(optimiser, InterTree)
317
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
318
        optimiser = InterTree.get(tree, rev_tree)
319
        self.assertIsInstance(optimiser, InterTree)
320
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
321
        tree.unlock()
322
323
    def test_empty_basis_to_dirstate_tree(self):
324
        # we should get a InterDirStateTree for doing
325
        # 'changes_from' from the first basis dirstate revision tree to a
326
        # WorkingTree4.
327
        tree = self.make_workingtree()
328
        tree.lock_read()
329
        basis_tree = tree.basis_tree()
330
        basis_tree.lock_read()
331
        optimiser = InterTree.get(basis_tree, tree)
332
        tree.unlock()
333
        basis_tree.unlock()
334
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
335
336
    def test_nonempty_basis_to_dirstate_tree(self):
337
        # we should get a InterDirStateTree for doing
338
        # 'changes_from' from a non-null basis dirstate revision tree to a
339
        # WorkingTree4.
340
        tree = self.make_workingtree()
341
        tree.commit('first post')
342
        tree.lock_read()
343
        basis_tree = tree.basis_tree()
344
        basis_tree.lock_read()
345
        optimiser = InterTree.get(basis_tree, tree)
346
        tree.unlock()
347
        basis_tree.unlock()
348
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
349
350
    def test_empty_basis_revtree_to_dirstate_tree(self):
351
        # we should get a InterDirStateTree for doing
352
        # 'changes_from' from an empty repository based rev tree to a
353
        # WorkingTree4.
354
        tree = self.make_workingtree()
355
        tree.lock_read()
356
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
357
        basis_tree.lock_read()
358
        optimiser = InterTree.get(basis_tree, tree)
359
        tree.unlock()
360
        basis_tree.unlock()
361
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
362
363
    def test_nonempty_basis_revtree_to_dirstate_tree(self):
364
        # we should get a InterDirStateTree for doing
365
        # 'changes_from' from a non-null repository based rev tree to a
366
        # WorkingTree4.
367
        tree = self.make_workingtree()
368
        tree.commit('first post')
369
        tree.lock_read()
370
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
371
        basis_tree.lock_read()
372
        optimiser = InterTree.get(basis_tree, tree)
373
        tree.unlock()
374
        basis_tree.unlock()
375
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
376
377
    def test_tree_to_basis_in_other_tree(self):
378
        # we should get a InterDirStateTree when
379
        # the source revid is in the dirstate object of the target and
380
        # the dirstates are different. This is largely covered by testing
381
        # with repository revtrees, so is just for extra confidence.
382
        tree = self.make_workingtree('a')
383
        tree.commit('first post')
384
        tree2 = self.make_workingtree('b')
385
        tree2.pull(tree.branch)
386
        basis_tree = tree.basis_tree()
387
        tree2.lock_read()
388
        basis_tree.lock_read()
389
        optimiser = InterTree.get(basis_tree, tree2)
390
        tree2.unlock()
391
        basis_tree.unlock()
392
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
393
394
    def test_merged_revtree_to_tree(self):
395
        # we should get a InterDirStateTree when
396
        # the source tree is a merged tree present in the dirstate of target.
397
        tree = self.make_workingtree('a')
398
        tree.commit('first post')
399
        tree.commit('tree 1 commit 2')
400
        tree2 = self.make_workingtree('b')
401
        tree2.pull(tree.branch)
402
        tree2.commit('tree 2 commit 2')
403
        tree.merge_from_branch(tree2.branch)
404
        second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
405
        second_parent_tree.lock_read()
406
        tree.lock_read()
407
        optimiser = InterTree.get(second_parent_tree, tree)
408
        tree.unlock()
409
        second_parent_tree.unlock()
410
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
411
412
    def test_id2path(self):
413
        tree = self.make_workingtree('tree')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
414
        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
415
        tree.add(['a'], ['a-id'])
416
        self.assertEqual(u'a', tree.id2path('a-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
417
        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
418
        tree.commit('a')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
419
        tree.add(['b'], ['b-id'])
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
420
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
421
        try:
422
            tree.rename_one('a', u'b\xb5rry')
423
            new_path = u'b\xb5rry'
424
        except UnicodeEncodeError:
425
            # support running the test on non-unicode platforms
426
            tree.rename_one('a', 'c')
427
            new_path = 'c'
428
        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
429
        tree.commit(u'b\xb5rry')
430
        tree.unversion(['a-id'])
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
431
        self.assertRaises(errors.NoSuchId, tree.id2path, 'a-id')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
432
        self.assertEqual('b', tree.id2path('b-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
433
        self.assertRaises(errors.NoSuchId, tree.id2path, 'c-id')
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
434
435
    def test_unique_root_id_per_tree(self):
436
        # 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
437
        format_name = 'dirstate-with-subtree'
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
438
        tree1 = self.make_branch_and_tree('tree1',
439
            format=format_name)
440
        tree2 = self.make_branch_and_tree('tree2',
441
            format=format_name)
442
        self.assertNotEqual(tree1.get_root_id(), tree2.get_root_id())
443
        # when you branch, it inherits the same root id
444
        rev1 = tree1.commit('first post')
445
        tree3 = tree1.bzrdir.sprout('tree3').open_workingtree()
446
        self.assertEqual(tree3.get_root_id(), tree1.get_root_id())
447
2255.11.2 by Martin Pool
Add more dirstate root-id-changing tests
448
    def test_set_root_id(self):
449
        # similar to some code that fails in the dirstate-plus-subtree branch
450
        # -- setting the root id while adding a parent seems to scramble the
451
        # dirstate invariants. -- mbp 20070303
452
        def validate():
453
            wt.lock_read()
454
            try:
455
                wt.current_dirstate()._validate()
456
            finally:
457
                wt.unlock()
458
        wt = self.make_workingtree('tree')
459
        wt.set_root_id('TREE-ROOTID')
460
        validate()
461
        wt.commit('somenthing')
462
        validate()
463
        # now switch and commit again
464
        wt.set_root_id('tree-rootid')
465
        validate()
466
        wt.commit('again')
467
        validate()
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
468
1551.15.6 by Aaron Bentley
Use ROOT_ID when the repository supports old clients (Bug #107168)
469
    def test_default_root_id(self):
470
        tree = self.make_branch_and_tree('tag', format='dirstate-tags')
471
        self.assertEqual(inventory.ROOT_ID, tree.get_root_id())
472
        tree = self.make_branch_and_tree('subtree',
473
                                         format='dirstate-with-subtree')
474
        self.assertNotEqual(inventory.ROOT_ID, tree.get_root_id())
475
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
476
    def test_non_subtree_with_nested_trees(self):
477
        # prior to dirstate, st/diff/commit ignored nested trees.
478
        # dirstate, as opposed to dirstate-with-subtree, should
479
        # behave the same way.
480
        tree = self.make_branch_and_tree('.', format='dirstate')
481
        self.assertFalse(tree.supports_tree_reference())
482
        self.build_tree(['dir/'])
483
        # for testing easily.
484
        tree.set_root_id('root')
485
        tree.add(['dir'], ['dir-id'])
486
        subtree = self.make_branch_and_tree('dir')
487
        # the most primitive operation: kind
488
        self.assertEqual('directory', tree.kind('dir-id'))
489
        # a diff against the basis should give us a directory
490
        tree.lock_read()
491
        expected = [('dir-id',
492
            (None, u'dir'),
493
            True,
494
            (False, True),
495
            (None, 'root'),
496
            (None, u'dir'),
497
            (None, 'directory'),
498
            (None, False))]
499
        self.assertEqual(expected, list(tree._iter_changes(tree.basis_tree(),
500
            specific_files=['dir'])))
501
        tree.unlock()
502
        # do a commit, we want to trigger the dirstate fast-path too
503
        tree.commit('first post')
504
        # change the path for the subdir, which will trigger getting all
505
        # its data:
506
        os.rename('dir', 'also-dir')
507
        # now the diff will use the fast path
508
        tree.lock_read()
509
        expected = [('dir-id',
510
            (u'dir', u'dir'),
511
            True,
512
            (True, True),
513
            ('root', 'root'),
514
            ('dir', 'dir'),
515
            ('directory', None),
516
            (False, False))]
517
        self.assertEqual(expected, list(tree._iter_changes(tree.basis_tree())))
518
        tree.unlock()
519
520
    def test_with_subtree_supports_tree_references(self):
521
        # dirstate-with-subtree should support tree-references.
522
        tree = self.make_branch_and_tree('.', format='dirstate-with-subtree')
523
        self.assertTrue(tree.supports_tree_reference())
524
        # having checked this is on, the tree interface, and intertree
525
        # interface tests, will proceed to test the subtree support of
526
        # workingtree_4.
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
527
528
    def test_iter_changes_ignores_unversioned_dirs(self):
529
        """_iter_changes should not descend into unversioned directories."""
530
        tree = self.make_branch_and_tree('.', format='dirstate')
531
        # We have an unversioned directory at the root, a versioned one with
532
        # other versioned files and an unversioned directory, and another
533
        # versioned dir with nothing but an unversioned directory.
534
        self.build_tree(['unversioned/',
535
                         'unversioned/a',
536
                         'unversioned/b/',
537
                         'versioned/',
538
                         'versioned/unversioned/',
539
                         'versioned/unversioned/a',
540
                         'versioned/unversioned/b/',
541
                         'versioned2/',
542
                         'versioned2/a',
543
                         'versioned2/unversioned/',
544
                         'versioned2/unversioned/a',
545
                         'versioned2/unversioned/b/',
546
                        ])
547
        tree.add(['versioned', 'versioned2', 'versioned2/a'])
548
        tree.commit('one', rev_id='rev-1')
549
        # Trap osutils._walkdirs_utf8 to spy on what dirs have been accessed.
550
        returned = []
551
        orig_walkdirs = osutils._walkdirs_utf8
552
        def reset():
553
            osutils._walkdirs_utf8 = orig_walkdirs
554
        self.addCleanup(reset)
555
        def walkdirs_spy(*args, **kwargs):
556
            for val in orig_walkdirs(*args, **kwargs):
557
                returned.append(val[0][0])
558
                yield val
559
        osutils._walkdirs_utf8 = walkdirs_spy
560
561
        basis = tree.basis_tree()
562
        tree.lock_read()
563
        self.addCleanup(tree.unlock)
564
        basis.lock_read()
565
        self.addCleanup(basis.unlock)
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
566
        changes = [c[1] for c in
567
                   tree._iter_changes(basis, want_unversioned=True)]
568
        self.assertEqual([(None, 'unversioned'),
569
                          (None, 'versioned/unversioned'),
570
                          (None, 'versioned2/unversioned'),
571
                         ], changes)
572
        self.assertEqual(['', 'versioned', 'versioned2'], returned)
573
        del returned[:] # reset
574
        changes = [c[1] for c in tree._iter_changes(basis)]
575
        self.assertEqual([], changes)
576
        self.assertEqual(['', 'versioned', 'versioned2'], returned)