/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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()
3462.1.7 by John Arbash Meinel
fix a test that assumed WT4.set_parent_trees() wouldn't filter the list.
118
        subtree3 = subtree.bzrdir.sprout('subdir3').open_workingtree()
119
        rev3 = subtree3.commit('merge from subdir2')
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
120
121
        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.
122
        repo.fetch(subtree.branch.repository, rev1)
123
        repo.fetch(subtree2.branch.repository, rev2)
124
        repo.fetch(subtree3.branch.repository, rev3)
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
125
        # will also pull the others...
126
127
        # 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.
128
        rev1_revtree = repo.revision_tree(rev1)
129
        rev2_revtree = repo.revision_tree(rev2)
130
        rev3_revtree = repo.revision_tree(rev3)
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
131
        # tree doesn't contain a text merge yet but we'll just
132
        # set the parents as if a merge had taken place. 
133
        # this should cause the tree data to be folded into the 
134
        # dirstate.
135
        tree.set_parent_trees([
136
            (rev1, rev1_revtree),
137
            (rev2, rev2_revtree),
138
            (rev3, rev3_revtree), ])
139
140
        # create tree-sourced revision trees
141
        rev1_tree = tree.revision_tree(rev1)
142
        rev1_tree.lock_read()
143
        self.addCleanup(rev1_tree.unlock)
144
        rev2_tree = tree.revision_tree(rev2)
145
        rev2_tree.lock_read()
146
        self.addCleanup(rev2_tree.unlock)
147
        rev3_tree = tree.revision_tree(rev3)
148
        rev3_tree.lock_read()
149
        self.addCleanup(rev3_tree.unlock)
150
151
        # now we should be able to get them back out
152
        self.assertTreesEqual(rev1_revtree, rev1_tree)
153
        self.assertTreesEqual(rev2_revtree, rev2_tree)
154
        self.assertTreesEqual(rev3_revtree, rev3_tree)
155
156
    def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
157
        """Setting parent trees on a dirstate working tree takes
158
        the trees it's given and doesn't need to read them from the 
159
        repository.
160
        """
161
        tree = self.make_workingtree()
162
163
        subtree = self.make_branch_and_tree('subdir')
164
        rev1 = subtree.commit('commit in subdir')
165
        rev1_tree = subtree.basis_tree()
166
        rev1_tree.lock_read()
167
        self.addCleanup(rev1_tree.unlock)
168
169
        tree.branch.pull(subtree.branch)
170
171
        # break the repository's legs to make sure it only uses the trees
172
        # it's given; any calls to forbidden methods will raise an 
173
        # AssertionError
174
        repo = tree.branch.repository
175
        repo.get_revision = self.fail
176
        repo.get_inventory = self.fail
177
        repo.get_inventory_xml = self.fail
178
        # try to set the parent trees.
179
        tree.set_parent_trees([(rev1, rev1_tree)])
180
181
    def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
182
        """Getting parent trees from a dirstate tree does not read from the 
183
        repos inventory store. This is an important part of the dirstate
184
        performance optimisation work.
185
        """
186
        tree = self.make_workingtree()
187
188
        subtree = self.make_branch_and_tree('subdir')
189
        # writelock the tree so its repository doesn't get readlocked by
190
        # the revision tree locks. This works around the bug where we dont
191
        # permit lock upgrading.
192
        subtree.lock_write()
193
        self.addCleanup(subtree.unlock)
194
        rev1 = subtree.commit('commit in subdir')
195
        rev1_tree = subtree.basis_tree()
196
        rev1_tree.lock_read()
197
        rev1_tree.inventory
198
        self.addCleanup(rev1_tree.unlock)
199
        rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
200
        rev2_tree = subtree.basis_tree()
201
        rev2_tree.lock_read()
202
        rev2_tree.inventory
203
        self.addCleanup(rev2_tree.unlock)
204
205
        tree.branch.pull(subtree.branch)
206
207
        # break the repository's legs to make sure it only uses the trees
208
        # it's given; any calls to forbidden methods will raise an 
209
        # AssertionError
210
        repo = tree.branch.repository
211
        # dont uncomment this: the revision object must be accessed to 
212
        # answer 'get_parent_ids' for the revision tree- dirstate does not 
213
        # cache the parents of a parent tree at this point.
214
        #repo.get_revision = self.fail
215
        repo.get_inventory = self.fail
216
        repo.get_inventory_xml = self.fail
217
        # set the parent trees.
218
        tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
219
        # read the first tree
220
        result_rev1_tree = tree.revision_tree(rev1)
221
        # read the second
222
        result_rev2_tree = tree.revision_tree(rev2)
223
        # compare - there should be no differences between the handed and 
224
        # returned trees
225
        self.assertTreesEqual(rev1_tree, result_rev1_tree)
226
        self.assertTreesEqual(rev2_tree, result_rev2_tree)
227
228
    def test_dirstate_doesnt_cache_non_parent_trees(self):
229
        """Getting parent trees from a dirstate tree does not read from the 
230
        repos inventory store. This is an important part of the dirstate
231
        performance optimisation work.
232
        """
233
        tree = self.make_workingtree()
234
235
        # make a tree that we can try for, which is able to be returned but
236
        # must not be
237
        subtree = self.make_branch_and_tree('subdir')
238
        rev1 = subtree.commit('commit in subdir')
239
        tree.branch.pull(subtree.branch)
240
        # check it fails
241
        self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
242
243
    def test_no_dirstate_outside_lock(self):
244
        # temporary test until the code is mature enough to test from outside.
245
        """Getting a dirstate object fails if there is no lock."""
246
        def lock_and_call_current_dirstate(tree, lock_method):
247
            getattr(tree, lock_method)()
248
            tree.current_dirstate()
249
            tree.unlock()
250
        tree = self.make_workingtree()
251
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
252
        lock_and_call_current_dirstate(tree, 'lock_read')
253
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
254
        lock_and_call_current_dirstate(tree, 'lock_write')
255
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
256
        lock_and_call_current_dirstate(tree, 'lock_tree_write')
257
        self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
258
259
    def test_new_dirstate_on_new_lock(self):
260
        # until we have detection for when a dirstate can be reused, we
261
        # want to reparse dirstate on every new lock.
262
        known_dirstates = set()
263
        def lock_and_compare_all_current_dirstate(tree, lock_method):
264
            getattr(tree, lock_method)()
265
            state = tree.current_dirstate()
266
            self.assertFalse(state in known_dirstates)
267
            known_dirstates.add(state)
268
            tree.unlock()
269
        tree = self.make_workingtree()
270
        # lock twice with each type to prevent silly per-lock-type bugs.
271
        # each lock and compare looks for a unique state object.
272
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
273
        lock_and_compare_all_current_dirstate(tree, 'lock_read')
274
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
275
        lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
276
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
277
        lock_and_compare_all_current_dirstate(tree, 'lock_write')
278
2255.2.122 by Robert Collins
Alter intertree implementation tests to let dirstate inter-trees be correctly parameterised.
279
    def test_constructing_invalid_interdirstate_raises(self):
280
        tree = self.make_workingtree()
281
        rev_id = tree.commit('first post')
282
        rev_id2 = tree.commit('second post')
283
        rev_tree = tree.branch.repository.revision_tree(rev_id)
284
        # Exception is not a great thing to raise, but this test is 
285
        # very short, and code is used to sanity check other tests, so 
286
        # a full error object is YAGNI.
287
        self.assertRaises(
288
            Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
289
        self.assertRaises(
290
            Exception, workingtree_4.InterDirStateTree, tree, rev_tree)
291
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
292
    def test_revtree_to_revtree_not_interdirstate(self):
293
        # we should not get a dirstate optimiser for two repository sourced
294
        # revtrees. we can't prove a negative, so we dont do exhaustive tests
295
        # of all formats; though that could be written in the future it doesn't
296
        # seem well worth it.
297
        tree = self.make_workingtree()
298
        rev_id = tree.commit('first post')
299
        rev_id2 = tree.commit('second post')
300
        rev_tree = tree.branch.repository.revision_tree(rev_id)
301
        rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
302
        optimiser = InterTree.get(rev_tree, rev_tree2)
303
        self.assertIsInstance(optimiser, InterTree)
304
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
305
        optimiser = InterTree.get(rev_tree2, rev_tree)
306
        self.assertIsInstance(optimiser, InterTree)
307
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
308
309
    def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
310
        # we should not get a dirstate optimiser when the revision id for of
311
        # the source is not in the dirstate of the target.
312
        tree = self.make_workingtree()
313
        rev_id = tree.commit('first post')
314
        rev_id2 = tree.commit('second post')
315
        rev_tree = tree.branch.repository.revision_tree(rev_id)
316
        tree.lock_read()
317
        optimiser = InterTree.get(rev_tree, tree)
318
        self.assertIsInstance(optimiser, InterTree)
319
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
320
        optimiser = InterTree.get(tree, rev_tree)
321
        self.assertIsInstance(optimiser, InterTree)
322
        self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
323
        tree.unlock()
324
325
    def test_empty_basis_to_dirstate_tree(self):
326
        # we should get a InterDirStateTree for doing
327
        # 'changes_from' from the first basis dirstate revision tree to a
328
        # WorkingTree4.
329
        tree = self.make_workingtree()
330
        tree.lock_read()
331
        basis_tree = tree.basis_tree()
332
        basis_tree.lock_read()
333
        optimiser = InterTree.get(basis_tree, tree)
334
        tree.unlock()
335
        basis_tree.unlock()
336
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
337
338
    def test_nonempty_basis_to_dirstate_tree(self):
339
        # we should get a InterDirStateTree for doing
340
        # 'changes_from' from a non-null basis dirstate revision tree to a
341
        # WorkingTree4.
342
        tree = self.make_workingtree()
343
        tree.commit('first post')
344
        tree.lock_read()
345
        basis_tree = tree.basis_tree()
346
        basis_tree.lock_read()
347
        optimiser = InterTree.get(basis_tree, tree)
348
        tree.unlock()
349
        basis_tree.unlock()
350
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
351
352
    def test_empty_basis_revtree_to_dirstate_tree(self):
353
        # we should get a InterDirStateTree for doing
354
        # 'changes_from' from an empty repository based rev tree to a
355
        # WorkingTree4.
356
        tree = self.make_workingtree()
357
        tree.lock_read()
358
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
359
        basis_tree.lock_read()
360
        optimiser = InterTree.get(basis_tree, tree)
361
        tree.unlock()
362
        basis_tree.unlock()
363
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
364
365
    def test_nonempty_basis_revtree_to_dirstate_tree(self):
366
        # we should get a InterDirStateTree for doing
367
        # 'changes_from' from a non-null repository based rev tree to a
368
        # WorkingTree4.
369
        tree = self.make_workingtree()
370
        tree.commit('first post')
371
        tree.lock_read()
372
        basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
373
        basis_tree.lock_read()
374
        optimiser = InterTree.get(basis_tree, tree)
375
        tree.unlock()
376
        basis_tree.unlock()
377
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
378
379
    def test_tree_to_basis_in_other_tree(self):
380
        # we should get a InterDirStateTree when
381
        # the source revid is in the dirstate object of the target and
382
        # the dirstates are different. This is largely covered by testing
383
        # with repository revtrees, so is just for extra confidence.
384
        tree = self.make_workingtree('a')
385
        tree.commit('first post')
386
        tree2 = self.make_workingtree('b')
387
        tree2.pull(tree.branch)
388
        basis_tree = tree.basis_tree()
389
        tree2.lock_read()
390
        basis_tree.lock_read()
391
        optimiser = InterTree.get(basis_tree, tree2)
392
        tree2.unlock()
393
        basis_tree.unlock()
394
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
395
396
    def test_merged_revtree_to_tree(self):
397
        # we should get a InterDirStateTree when
398
        # the source tree is a merged tree present in the dirstate of target.
399
        tree = self.make_workingtree('a')
400
        tree.commit('first post')
401
        tree.commit('tree 1 commit 2')
402
        tree2 = self.make_workingtree('b')
403
        tree2.pull(tree.branch)
404
        tree2.commit('tree 2 commit 2')
405
        tree.merge_from_branch(tree2.branch)
406
        second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
407
        second_parent_tree.lock_read()
408
        tree.lock_read()
409
        optimiser = InterTree.get(second_parent_tree, tree)
410
        tree.unlock()
411
        second_parent_tree.unlock()
412
        self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
413
414
    def test_id2path(self):
415
        tree = self.make_workingtree('tree')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
416
        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
417
        tree.add(['a'], ['a-id'])
418
        self.assertEqual(u'a', tree.id2path('a-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
419
        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
420
        tree.commit('a')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
421
        tree.add(['b'], ['b-id'])
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
422
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
423
        try:
2825.6.1 by Robert Collins
* ``WorkingTree.rename_one`` will now raise an error if normalisation of the
424
            new_path = u'b\u03bcrry'
425
            tree.rename_one('a', new_path)
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
426
        except UnicodeEncodeError:
427
            # support running the test on non-unicode platforms
428
            new_path = 'c'
2825.6.1 by Robert Collins
* ``WorkingTree.rename_one`` will now raise an error if normalisation of the
429
            tree.rename_one('a', new_path)
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
430
        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
431
        tree.commit(u'b\xb5rry')
432
        tree.unversion(['a-id'])
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
433
        self.assertRaises(errors.NoSuchId, tree.id2path, 'a-id')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
434
        self.assertEqual('b', tree.id2path('b-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
435
        self.assertRaises(errors.NoSuchId, tree.id2path, 'c-id')
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
436
437
    def test_unique_root_id_per_tree(self):
438
        # 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
439
        format_name = 'dirstate-with-subtree'
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
440
        tree1 = self.make_branch_and_tree('tree1',
441
            format=format_name)
442
        tree2 = self.make_branch_and_tree('tree2',
443
            format=format_name)
444
        self.assertNotEqual(tree1.get_root_id(), tree2.get_root_id())
445
        # when you branch, it inherits the same root id
446
        rev1 = tree1.commit('first post')
447
        tree3 = tree1.bzrdir.sprout('tree3').open_workingtree()
448
        self.assertEqual(tree3.get_root_id(), tree1.get_root_id())
449
2255.11.2 by Martin Pool
Add more dirstate root-id-changing tests
450
    def test_set_root_id(self):
451
        # similar to some code that fails in the dirstate-plus-subtree branch
452
        # -- setting the root id while adding a parent seems to scramble the
453
        # dirstate invariants. -- mbp 20070303
454
        def validate():
455
            wt.lock_read()
456
            try:
457
                wt.current_dirstate()._validate()
458
            finally:
459
                wt.unlock()
460
        wt = self.make_workingtree('tree')
461
        wt.set_root_id('TREE-ROOTID')
462
        validate()
463
        wt.commit('somenthing')
464
        validate()
465
        # now switch and commit again
466
        wt.set_root_id('tree-rootid')
467
        validate()
468
        wt.commit('again')
469
        validate()
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
470
1551.15.6 by Aaron Bentley
Use ROOT_ID when the repository supports old clients (Bug #107168)
471
    def test_default_root_id(self):
472
        tree = self.make_branch_and_tree('tag', format='dirstate-tags')
473
        self.assertEqual(inventory.ROOT_ID, tree.get_root_id())
474
        tree = self.make_branch_and_tree('subtree',
475
                                         format='dirstate-with-subtree')
476
        self.assertNotEqual(inventory.ROOT_ID, tree.get_root_id())
477
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
478
    def test_non_subtree_with_nested_trees(self):
479
        # prior to dirstate, st/diff/commit ignored nested trees.
480
        # dirstate, as opposed to dirstate-with-subtree, should
481
        # behave the same way.
482
        tree = self.make_branch_and_tree('.', format='dirstate')
483
        self.assertFalse(tree.supports_tree_reference())
484
        self.build_tree(['dir/'])
485
        # for testing easily.
486
        tree.set_root_id('root')
487
        tree.add(['dir'], ['dir-id'])
488
        subtree = self.make_branch_and_tree('dir')
489
        # the most primitive operation: kind
490
        self.assertEqual('directory', tree.kind('dir-id'))
491
        # a diff against the basis should give us a directory
492
        tree.lock_read()
493
        expected = [('dir-id',
494
            (None, u'dir'),
495
            True,
496
            (False, True),
497
            (None, 'root'),
498
            (None, u'dir'),
499
            (None, 'directory'),
500
            (None, False))]
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
501
        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.
502
            specific_files=['dir'])))
503
        tree.unlock()
504
        # do a commit, we want to trigger the dirstate fast-path too
505
        tree.commit('first post')
506
        # change the path for the subdir, which will trigger getting all
507
        # its data:
508
        os.rename('dir', 'also-dir')
509
        # now the diff will use the fast path
510
        tree.lock_read()
511
        expected = [('dir-id',
512
            (u'dir', u'dir'),
513
            True,
514
            (True, True),
515
            ('root', 'root'),
516
            ('dir', 'dir'),
517
            ('directory', None),
518
            (False, False))]
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
519
        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.
520
        tree.unlock()
521
522
    def test_with_subtree_supports_tree_references(self):
523
        # dirstate-with-subtree should support tree-references.
524
        tree = self.make_branch_and_tree('.', format='dirstate-with-subtree')
525
        self.assertTrue(tree.supports_tree_reference())
526
        # having checked this is on, the tree interface, and intertree
527
        # interface tests, will proceed to test the subtree support of
528
        # workingtree_4.
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
529
530
    def test_iter_changes_ignores_unversioned_dirs(self):
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
531
        """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.
532
        tree = self.make_branch_and_tree('.', format='dirstate')
533
        # We have an unversioned directory at the root, a versioned one with
534
        # other versioned files and an unversioned directory, and another
535
        # versioned dir with nothing but an unversioned directory.
536
        self.build_tree(['unversioned/',
537
                         'unversioned/a',
538
                         'unversioned/b/',
539
                         'versioned/',
540
                         'versioned/unversioned/',
541
                         'versioned/unversioned/a',
542
                         'versioned/unversioned/b/',
543
                         'versioned2/',
544
                         'versioned2/a',
545
                         'versioned2/unversioned/',
546
                         'versioned2/unversioned/a',
547
                         'versioned2/unversioned/b/',
548
                        ])
549
        tree.add(['versioned', 'versioned2', 'versioned2/a'])
550
        tree.commit('one', rev_id='rev-1')
551
        # Trap osutils._walkdirs_utf8 to spy on what dirs have been accessed.
552
        returned = []
553
        orig_walkdirs = osutils._walkdirs_utf8
554
        def reset():
555
            osutils._walkdirs_utf8 = orig_walkdirs
556
        self.addCleanup(reset)
557
        def walkdirs_spy(*args, **kwargs):
558
            for val in orig_walkdirs(*args, **kwargs):
559
                returned.append(val[0][0])
560
                yield val
561
        osutils._walkdirs_utf8 = walkdirs_spy
562
563
        basis = tree.basis_tree()
564
        tree.lock_read()
565
        self.addCleanup(tree.unlock)
566
        basis.lock_read()
567
        self.addCleanup(basis.unlock)
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
568
        changes = [c[1] for c in
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
569
                   tree.iter_changes(basis, want_unversioned=True)]
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
570
        self.assertEqual([(None, 'unversioned'),
571
                          (None, 'versioned/unversioned'),
572
                          (None, 'versioned2/unversioned'),
573
                         ], changes)
574
        self.assertEqual(['', 'versioned', 'versioned2'], returned)
575
        del returned[:] # reset
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
576
        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
577
        self.assertEqual([], changes)
578
        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.
579
580
581
class TestCorruptDirstate(TestCaseWithTransport):
582
    """Tests for how we handle when the dirstate has been corrupted."""
583
584
    def create_wt4(self):
585
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
586
        control.create_repository()
587
        control.create_branch()
588
        tree = workingtree_4.WorkingTreeFormat4().initialize(control)
589
        return tree
590
591
    def test_invalid_rename(self):
592
        tree = self.create_wt4()
593
        # Create a corrupted dirstate
594
        tree.lock_write()
3207.2.2 by John Arbash Meinel
Fix bug #187169, when an invalid delta is supplied to update_basis_by_delta
595
        try:
596
            tree.commit('init') # We need a parent, or we always compare with NULL
597
            state = tree.current_dirstate()
598
            state._read_dirblocks_if_needed()
599
            # Now add in an invalid entry, a rename with a dangling pointer
600
            state._dirblocks[1][1].append((('', 'foo', 'foo-id'),
601
                                            [('f', '', 0, False, ''),
602
                                             ('r', 'bar', 0 , False, '')]))
603
            self.assertListRaises(errors.CorruptDirstate,
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
604
                                  tree.iter_changes, tree.basis_tree())
3207.2.2 by John Arbash Meinel
Fix bug #187169, when an invalid delta is supplied to update_basis_by_delta
605
        finally:
606
            tree.unlock()
607
608
    def get_simple_dirblocks(self, state):
609
        """Extract the simple information from the DirState.
610
611
        This returns the dirblocks, only with the sha1sum and stat details
612
        filtered out.
613
        """
614
        simple_blocks = []
615
        for block in state._dirblocks:
616
            simple_block = (block[0], [])
617
            for entry in block[1]:
618
                # Include the key for each entry, and for each parent include
619
                # just the minikind, so we know if it was
620
                # present/absent/renamed/etc
621
                simple_block[1].append((entry[0], [i[0] for i in entry[1]]))
622
            simple_blocks.append(simple_block)
623
        return simple_blocks
624
625
    def test_update_basis_with_invalid_delta(self):
626
        """When given an invalid delta, it should abort, and not be saved."""
627
        self.build_tree(['dir/', 'dir/file'])
628
        tree = self.create_wt4()
629
        tree.lock_write()
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
630
        self.addCleanup(tree.unlock)
3207.2.2 by John Arbash Meinel
Fix bug #187169, when an invalid delta is supplied to update_basis_by_delta
631
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
632
        first_revision_id = tree.commit('init')
633
634
        root_id = tree.path2id('')
635
        state = tree.current_dirstate()
636
        state._read_dirblocks_if_needed()
637
        self.assertEqual([
638
            ('', [(('', '', root_id), ['d', 'd'])]),
639
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
640
            ('dir', [(('dir', 'file', 'file-id'), ['f', 'f'])]),
641
        ],  self.get_simple_dirblocks(state))
642
643
        tree.remove(['dir/file'])
644
        self.assertEqual([
645
            ('', [(('', '', root_id), ['d', 'd'])]),
646
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
647
            ('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
648
        ],  self.get_simple_dirblocks(state))
649
        # Make sure the removal is written to disk
650
        tree.flush()
651
652
        # self.assertRaises(Exception, tree.update_basis_by_delta,
653
        new_dir = inventory.InventoryDirectory('dir-id', 'new-dir', root_id)
654
        new_dir.revision = 'new-revision-id'
655
        new_file = inventory.InventoryFile('file-id', 'new-file', root_id)
656
        new_file.revision = 'new-revision-id'
657
        self.assertRaises(errors.InconsistentDelta,
658
            tree.update_basis_by_delta, 'new-revision-id',
659
            [('dir', 'new-dir', 'dir-id', new_dir),
660
             ('dir/file', 'new-dir/new-file', 'file-id', new_file),
661
            ])
662
        del state
663
664
        # Now when we re-read the file it should not have been modified
665
        tree.unlock()
666
        tree.lock_read()
667
        self.assertEqual(first_revision_id, tree.last_revision())
668
        state = tree.current_dirstate()
669
        state._read_dirblocks_if_needed()
670
        self.assertEqual([
671
            ('', [(('', '', root_id), ['d', 'd'])]),
672
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
673
            ('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
674
        ],  self.get_simple_dirblocks(state))