/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_workingtree_4.py

Merge with prepare-shelf

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
20
import os
 
21
import time
 
22
 
 
23
from bzrlib import (
 
24
    bzrdir,
 
25
    dirstate,
 
26
    errors,
 
27
    inventory,
 
28
    osutils,
 
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)
 
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'))
 
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:
 
62
 
 
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
 
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)
 
75
        tree.lock_write()
 
76
        self.assertTrue(our_lock.peek())
 
77
        tree.unlock()
 
78
        self.assertEquals(our_lock.peek(), None)
 
79
 
 
80
    def make_workingtree(self, relpath=''):
 
81
        url = self.get_url(relpath)
 
82
        if relpath:
 
83
            self.build_tree([relpath + '/'])
 
84
        dir = bzrdir.BzrDirMetaFormat1().initialize(url)
 
85
        repo = dir.create_repository()
 
86
        branch = dir.create_branch()
 
87
        try:
 
88
            return workingtree_4.WorkingTreeFormat4().initialize(dir)
 
89
        except errors.NotLocalUrl:
 
90
            raise TestSkipped('Not a local URL')
 
91
 
 
92
    def test_dirstate_stores_all_parent_inventories(self):
 
93
        tree = self.make_workingtree()
 
94
 
 
95
        # We're going to build in tree a working tree 
 
96
        # with three parent trees, with some files in common.  
 
97
    
 
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.
 
102
 
 
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.
 
107
        subtree.lock_write()
 
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')
 
112
 
 
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')
 
117
 
 
118
        subtree.flush()
 
119
        subtree3 = subtree.bzrdir.sprout('subdir3').open_workingtree()
 
120
        rev3 = subtree3.commit('merge from subdir2')
 
121
 
 
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...
 
127
 
 
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 
 
135
        # dirstate.
 
136
        tree.set_parent_trees([
 
137
            (rev1, rev1_revtree),
 
138
            (rev2, rev2_revtree),
 
139
            (rev3, rev3_revtree), ])
 
140
 
 
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)
 
151
 
 
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)
 
156
 
 
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 
 
160
        repository.
 
161
        """
 
162
        tree = self.make_workingtree()
 
163
 
 
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)
 
169
 
 
170
        tree.branch.pull(subtree.branch)
 
171
 
 
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 
 
174
        # AssertionError
 
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)])
 
181
 
 
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.
 
186
        """
 
187
        tree = self.make_workingtree()
 
188
 
 
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.
 
193
        subtree.lock_write()
 
194
        self.addCleanup(subtree.unlock)
 
195
        rev1 = subtree.commit('commit in subdir')
 
196
        rev1_tree = subtree.basis_tree()
 
197
        rev1_tree.lock_read()
 
198
        rev1_tree.inventory
 
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()
 
203
        rev2_tree.inventory
 
204
        self.addCleanup(rev2_tree.unlock)
 
205
 
 
206
        tree.branch.pull(subtree.branch)
 
207
 
 
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 
 
210
        # AssertionError
 
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)
 
222
        # read the second
 
223
        result_rev2_tree = tree.revision_tree(rev2)
 
224
        # compare - there should be no differences between the handed and 
 
225
        # returned trees
 
226
        self.assertTreesEqual(rev1_tree, result_rev1_tree)
 
227
        self.assertTreesEqual(rev2_tree, result_rev2_tree)
 
228
 
 
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.
 
233
        """
 
234
        tree = self.make_workingtree()
 
235
 
 
236
        # make a tree that we can try for, which is able to be returned but
 
237
        # must not be
 
238
        subtree = self.make_branch_and_tree('subdir')
 
239
        rev1 = subtree.commit('commit in subdir')
 
240
        tree.branch.pull(subtree.branch)
 
241
        # check it fails
 
242
        self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
 
243
 
 
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()
 
250
            tree.unlock()
 
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)
 
259
 
 
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)
 
269
            tree.unlock()
 
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')
 
279
 
 
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.
 
288
        self.assertRaises(
 
289
            Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
 
290
        self.assertRaises(
 
291
            Exception, workingtree_4.InterDirStateTree, tree, rev_tree)
 
292
 
 
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))
 
309
 
 
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)
 
317
        tree.lock_read()
 
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))
 
324
        tree.unlock()
 
325
 
 
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
 
329
        # WorkingTree4.
 
330
        tree = self.make_workingtree()
 
331
        tree.lock_read()
 
332
        basis_tree = tree.basis_tree()
 
333
        basis_tree.lock_read()
 
334
        optimiser = InterTree.get(basis_tree, tree)
 
335
        tree.unlock()
 
336
        basis_tree.unlock()
 
337
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
 
338
 
 
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
 
342
        # WorkingTree4.
 
343
        tree = self.make_workingtree()
 
344
        tree.commit('first post')
 
345
        tree.lock_read()
 
346
        basis_tree = tree.basis_tree()
 
347
        basis_tree.lock_read()
 
348
        optimiser = InterTree.get(basis_tree, tree)
 
349
        tree.unlock()
 
350
        basis_tree.unlock()
 
351
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
 
352
 
 
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
 
356
        # WorkingTree4.
 
357
        tree = self.make_workingtree()
 
358
        tree.lock_read()
 
359
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
 
360
        basis_tree.lock_read()
 
361
        optimiser = InterTree.get(basis_tree, tree)
 
362
        tree.unlock()
 
363
        basis_tree.unlock()
 
364
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
 
365
 
 
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
 
369
        # WorkingTree4.
 
370
        tree = self.make_workingtree()
 
371
        tree.commit('first post')
 
372
        tree.lock_read()
 
373
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
 
374
        basis_tree.lock_read()
 
375
        optimiser = InterTree.get(basis_tree, tree)
 
376
        tree.unlock()
 
377
        basis_tree.unlock()
 
378
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
 
379
 
 
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()
 
390
        tree2.lock_read()
 
391
        basis_tree.lock_read()
 
392
        optimiser = InterTree.get(basis_tree, tree2)
 
393
        tree2.unlock()
 
394
        basis_tree.unlock()
 
395
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
 
396
 
 
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()
 
409
        tree.lock_read()
 
410
        optimiser = InterTree.get(second_parent_tree, tree)
 
411
        tree.unlock()
 
412
        second_parent_tree.unlock()
 
413
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
 
414
 
 
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')
 
421
        tree.commit('a')
 
422
        tree.add(['b'], ['b-id'])
 
423
 
 
424
        try:
 
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
 
429
            new_path = 'c'
 
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')
 
437
 
 
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',
 
442
            format=format_name)
 
443
        tree2 = self.make_branch_and_tree('tree2',
 
444
            format=format_name)
 
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())
 
450
 
 
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
 
455
        def validate():
 
456
            wt.lock_read()
 
457
            try:
 
458
                wt.current_dirstate()._validate()
 
459
            finally:
 
460
                wt.unlock()
 
461
        wt = self.make_workingtree('tree')
 
462
        wt.set_root_id('TREE-ROOTID')
 
463
        validate()
 
464
        wt.commit('somenthing')
 
465
        validate()
 
466
        # now switch and commit again
 
467
        wt.set_root_id('tree-rootid')
 
468
        validate()
 
469
        wt.commit('again')
 
470
        validate()
 
471
 
 
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())
 
478
 
 
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
 
493
        tree.lock_read()
 
494
        expected = [('dir-id',
 
495
            (None, u'dir'),
 
496
            True,
 
497
            (False, True),
 
498
            (None, 'root'),
 
499
            (None, u'dir'),
 
500
            (None, 'directory'),
 
501
            (None, False))]
 
502
        self.assertEqual(expected, list(tree.iter_changes(tree.basis_tree(),
 
503
            specific_files=['dir'])))
 
504
        tree.unlock()
 
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
 
508
        # its data:
 
509
        os.rename('dir', 'also-dir')
 
510
        # now the diff will use the fast path
 
511
        tree.lock_read()
 
512
        expected = [('dir-id',
 
513
            (u'dir', u'dir'),
 
514
            True,
 
515
            (True, True),
 
516
            ('root', 'root'),
 
517
            ('dir', 'dir'),
 
518
            ('directory', None),
 
519
            (False, False))]
 
520
        self.assertEqual(expected, list(tree.iter_changes(tree.basis_tree())))
 
521
        tree.unlock()
 
522
 
 
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
 
529
        # workingtree_4.
 
530
 
 
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/',
 
538
                         'unversioned/a',
 
539
                         'unversioned/b/',
 
540
                         'versioned/',
 
541
                         'versioned/unversioned/',
 
542
                         'versioned/unversioned/a',
 
543
                         'versioned/unversioned/b/',
 
544
                         'versioned2/',
 
545
                         'versioned2/a',
 
546
                         'versioned2/unversioned/',
 
547
                         'versioned2/unversioned/a',
 
548
                         'versioned2/unversioned/b/',
 
549
                        ])
 
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.
 
553
        returned = []
 
554
        orig_walkdirs = osutils._walkdirs_utf8
 
555
        def reset():
 
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])
 
561
                yield val
 
562
        osutils._walkdirs_utf8 = walkdirs_spy
 
563
 
 
564
        basis = tree.basis_tree()
 
565
        tree.lock_read()
 
566
        self.addCleanup(tree.unlock)
 
567
        basis.lock_read()
 
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'),
 
574
                         ], changes)
 
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)
 
580
 
 
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.
 
587
        time.sleep(4)
 
588
        return tree
 
589
 
 
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.
 
594
        tree.lock_read()
 
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])
 
598
 
 
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")
 
603
        tree.lock_write()
 
604
        try:
 
605
            tree._observed_sha1("foo-id", "foo", (expected_sha1, statvalue))
 
606
            self.assertEqual(expected_sha1,
 
607
                tree._get_entry(path="foo")[1][0][1])
 
608
        finally:
 
609
            tree.unlock()
 
610
        tree = tree.bzrdir.open_workingtree()
 
611
        tree.lock_read()
 
612
        self.addCleanup(tree.unlock)
 
613
        self.assertEqual(expected_sha1, tree._get_entry(path="foo")[1][0][1])
 
614
 
 
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'])
 
619
        tree.lock_read()
 
620
        try:
 
621
            current_sha1 = tree._get_entry(path="foo")[1][0][1]
 
622
        finally:
 
623
            tree.unlock()
 
624
        tree.lock_write()
 
625
        try:
 
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])
 
631
        finally:
 
632
            tree.unlock()
 
633
 
 
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'])
 
639
        tree.lock_read()
 
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())
 
645
 
 
646
 
 
647
class TestCorruptDirstate(TestCaseWithTransport):
 
648
    """Tests for how we handle when the dirstate has been corrupted."""
 
649
 
 
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)
 
655
        return tree
 
656
 
 
657
    def test_invalid_rename(self):
 
658
        tree = self.create_wt4()
 
659
        # Create a corrupted dirstate
 
660
        tree.lock_write()
 
661
        try:
 
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())
 
671
        finally:
 
672
            tree.unlock()
 
673
 
 
674
    def get_simple_dirblocks(self, state):
 
675
        """Extract the simple information from the DirState.
 
676
 
 
677
        This returns the dirblocks, only with the sha1sum and stat details
 
678
        filtered out.
 
679
        """
 
680
        simple_blocks = []
 
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)
 
689
        return simple_blocks
 
690
 
 
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()
 
695
        tree.lock_write()
 
696
        self.addCleanup(tree.unlock)
 
697
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
 
698
        first_revision_id = tree.commit('init')
 
699
 
 
700
        root_id = tree.path2id('')
 
701
        state = tree.current_dirstate()
 
702
        state._read_dirblocks_if_needed()
 
703
        self.assertEqual([
 
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))
 
708
 
 
709
        tree.remove(['dir/file'])
 
710
        self.assertEqual([
 
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
 
716
        tree.flush()
 
717
 
 
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),
 
727
            ])
 
728
        del state
 
729
 
 
730
        # Now when we re-read the file it should not have been modified
 
731
        tree.unlock()
 
732
        tree.lock_read()
 
733
        self.assertEqual(first_revision_id, tree.last_revision())
 
734
        state = tree.current_dirstate()
 
735
        state._read_dirblocks_if_needed()
 
736
        self.assertEqual([
 
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))