/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
4183.7.1 by Sabin Iacob
update FSF mailing address
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
17
18
"""Tests for WorkingTreeFormat4"""
19
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
20
import os
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
21
import time
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
22
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
23
from bzrlib import (
24
    bzrdir,
25
    dirstate,
26
    errors,
1551.15.6 by Aaron Bentley
Use ROOT_ID when the repository supports old clients (Bug #107168)
27
    inventory,
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
28
    osutils,
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
29
    workingtree_4,
30
    )
31
from bzrlib.lockdir import LockDir
32
from bzrlib.tests import TestCaseWithTransport, TestSkipped
33
from bzrlib.tree import InterTree
34
35
36
class TestWorkingTreeFormat4(TestCaseWithTransport):
37
    """Tests specific to WorkingTreeFormat4."""
38
39
    def test_disk_layout(self):
40
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
41
        control.create_repository()
42
        control.create_branch()
43
        tree = workingtree_4.WorkingTreeFormat4().initialize(control)
44
        # we want:
45
        # format 'Bazaar Working Tree format 4'
46
        # stat-cache = ??
47
        t = control.get_workingtree_transport(None)
2255.2.230 by Robert Collins
Update tree format signatures to mention introducing bzr version.
48
        self.assertEqualDiff('Bazaar Working Tree Format 4 (bzr 0.15)\n',
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
49
                             t.get('format').read())
50
        self.assertFalse(t.has('inventory.basis'))
51
        # no last-revision file means 'None' or 'NULLREVISION'
52
        self.assertFalse(t.has('last-revision'))
53
        state = dirstate.DirState.on_file(t.local_abspath('dirstate'))
54
        state.lock_read()
55
        try:
56
            self.assertEqual([], state.get_parent_ids())
57
        finally:
58
            state.unlock()
59
60
    def test_uses_lockdir(self):
61
        """WorkingTreeFormat4 uses its own LockDir:
2255.10.1 by John Arbash Meinel
Update WorkingTree4 so that it doesn't use a HashCache,
62
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
63
            - lock is a directory
64
            - when the WorkingTree is locked, LockDir can see that
65
        """
66
        # this test could be factored into a subclass of tests common to both
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
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
95
        # We're going to build in tree a working tree
96
        # with three parent trees, with some files in common.
97
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
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()
3462.1.7 by John Arbash Meinel
fix a test that assumed WT4.set_parent_trees() wouldn't filter the list.
119
        subtree3 = subtree.bzrdir.sprout('subdir3').open_workingtree()
120
        rev3 = subtree3.commit('merge from subdir2')
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
121
122
        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.
123
        repo.fetch(subtree.branch.repository, rev1)
124
        repo.fetch(subtree2.branch.repository, rev2)
125
        repo.fetch(subtree3.branch.repository, rev3)
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
126
        # will also pull the others...
127
128
        # 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.
129
        rev1_revtree = repo.revision_tree(rev1)
130
        rev2_revtree = repo.revision_tree(rev2)
131
        rev3_revtree = repo.revision_tree(rev3)
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
132
        # tree doesn't contain a text merge yet but we'll just
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
133
        # set the parents as if a merge had taken place.
134
        # this should cause the tree data to be folded into the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
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
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
159
        the trees it's given and doesn't need to read them from the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
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
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
173
        # it's given; any calls to forbidden methods will raise an
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
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):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
183
        """Getting parent trees from a dirstate tree does not read from the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
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
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
209
        # it's given; any calls to forbidden methods will raise an
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
210
        # AssertionError
211
        repo = tree.branch.repository
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
212
        # dont uncomment this: the revision object must be accessed to
213
        # answer 'get_parent_ids' for the revision tree- dirstate does not
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
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)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
224
        # compare - there should be no differences between the handed and
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
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):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
230
        """Getting parent trees from a dirstate tree does not read from the
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
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
2255.2.122 by Robert Collins
Alter intertree implementation tests to let dirstate inter-trees be correctly parameterised.
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)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
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
2255.2.122 by Robert Collins
Alter intertree implementation tests to let dirstate inter-trees be correctly parameterised.
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
2255.2.121 by John Arbash Meinel
split out the WorkingTreeFormat4 tests into a separate test file
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)
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
414
415
    def test_id2path(self):
416
        tree = self.make_workingtree('tree')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
417
        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
418
        tree.add(['a'], ['a-id'])
419
        self.assertEqual(u'a', tree.id2path('a-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
420
        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
421
        tree.commit('a')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
422
        tree.add(['b'], ['b-id'])
2255.2.144 by John Arbash Meinel
Simplify update_minimal a bit more, by making id_index a
423
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
424
        try:
2825.6.1 by Robert Collins
* ``WorkingTree.rename_one`` will now raise an error if normalisation of the
425
            new_path = u'b\u03bcrry'
426
            tree.rename_one('a', new_path)
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
427
        except UnicodeEncodeError:
428
            # support running the test on non-unicode platforms
429
            new_path = 'c'
2825.6.1 by Robert Collins
* ``WorkingTree.rename_one`` will now raise an error if normalisation of the
430
            tree.rename_one('a', new_path)
2321.1.2 by Robert Collins
Skip new tests that depend on unicode file paths.
431
        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
432
        tree.commit(u'b\xb5rry')
433
        tree.unversion(['a-id'])
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
434
        self.assertRaises(errors.NoSuchId, tree.id2path, 'a-id')
2255.2.147 by John Arbash Meinel
Move fast id => path lookups down into DirState
435
        self.assertEqual('b', tree.id2path('b-id'))
2255.11.5 by Martin Pool
Tree.id2path should raise NoSuchId, not return None.
436
        self.assertRaises(errors.NoSuchId, tree.id2path, 'c-id')
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
437
438
    def test_unique_root_id_per_tree(self):
439
        # 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
440
        format_name = 'dirstate-with-subtree'
2255.2.166 by Martin Pool
(broken) Add Tree.get_root_id() & test
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
2255.11.2 by Martin Pool
Add more dirstate root-id-changing tests
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()
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
471
1551.15.6 by Aaron Bentley
Use ROOT_ID when the repository supports old clients (Bug #107168)
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
2255.2.232 by Robert Collins
Make WorkingTree4 report support for references based on the repositories capabilities.
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))]
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
502
        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.
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))]
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
520
        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.
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.
2466.4.1 by John Arbash Meinel
Add a (failing) test that exposes how _iter_changes is accidentally walking into unversioned directories.
530
531
    def test_iter_changes_ignores_unversioned_dirs(self):
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
532
        """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.
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)
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
569
        changes = [c[1] for c in
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
570
                   tree.iter_changes(basis, want_unversioned=True)]
2466.4.2 by John Arbash Meinel
Clean up the (failing) test so that the last thing
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
3254.1.1 by Aaron Bentley
Make Tree.iter_changes a public method
577
        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
578
        self.assertEqual([], changes)
579
        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.
580
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
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'])
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
585
        # a 4 second old timestamp is always hashable - sucks to delay
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
586
        # the test suite, but not testing this is worse.
587
        time.sleep(4)
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
588
        return tree
589
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
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()
4100.2.3 by Aaron Bentley
Avoid autodetecting tree references in _comparison_data.
595
        self.addCleanup(tree.unlock)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
596
        entry = tree._get_entry(path='foo')
597
        expected_sha1 = osutils.sha_file_by_name('foo')
598
        self.assertEqual(expected_sha1, entry[1][0][1])
599
600
    def test_observed_sha1_cachable(self):
601
        tree = self.get_tree_with_cachable_file_foo()
602
        expected_sha1 = osutils.sha_file_by_name('foo')
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
603
        statvalue = os.lstat("foo")
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
604
        tree.lock_write()
3207.2.2 by John Arbash Meinel
Fix bug #187169, when an invalid delta is supplied to update_basis_by_delta
605
        try:
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
606
            tree._observed_sha1("foo-id", "foo", (expected_sha1, statvalue))
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
607
            self.assertEqual(expected_sha1,
608
                tree._get_entry(path="foo")[1][0][1])
3207.2.2 by John Arbash Meinel
Fix bug #187169, when an invalid delta is supplied to update_basis_by_delta
609
        finally:
610
            tree.unlock()
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
611
        tree = tree.bzrdir.open_workingtree()
612
        tree.lock_read()
3207.2.1 by jameinel
Add a test that _iter_changes raises a clearer error when we encounter an invalid rename.
613
        self.addCleanup(tree.unlock)
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
614
        self.assertEqual(expected_sha1, tree._get_entry(path="foo")[1][0][1])
615
616
    def test_observed_sha1_new_file(self):
617
        tree = self.make_branch_and_tree('.')
618
        self.build_tree(['foo'])
619
        tree.add(['foo'], ['foo-id'])
3207.2.2 by John Arbash Meinel
Fix bug #187169, when an invalid delta is supplied to update_basis_by_delta
620
        tree.lock_read()
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
621
        try:
622
            current_sha1 = tree._get_entry(path="foo")[1][0][1]
623
        finally:
624
            tree.unlock()
625
        tree.lock_write()
626
        try:
627
            tree._observed_sha1("foo-id", "foo",
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
628
                (osutils.sha_file_by_name('foo'), os.lstat("foo")))
3709.3.1 by Robert Collins
First cut - make it work - at updating the tree stat cache during commit.
629
            # Must not have changed
630
            self.assertEqual(current_sha1,
631
                tree._get_entry(path="foo")[1][0][1])
632
        finally:
633
            tree.unlock()
3709.3.2 by Robert Collins
Race-free stat-fingerprint updating during commit via a new method get_file_with_stat.
634
635
    def test_get_file_with_stat_id_only(self):
636
        # Explicit test to ensure we get a lstat value from WT4 trees.
637
        tree = self.make_branch_and_tree('.')
638
        self.build_tree(['foo'])
639
        tree.add(['foo'], ['foo-id'])
640
        tree.lock_read()
641
        self.addCleanup(tree.unlock)
642
        file_obj, statvalue = tree.get_file_with_stat('foo-id')
643
        expected = os.lstat('foo')
644
        self.assertEqualStat(expected, statvalue)
645
        self.assertEqual(["contents of foo\n"], file_obj.readlines())
646
647
648
class TestCorruptDirstate(TestCaseWithTransport):
649
    """Tests for how we handle when the dirstate has been corrupted."""
650
651
    def create_wt4(self):
652
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
653
        control.create_repository()
654
        control.create_branch()
655
        tree = workingtree_4.WorkingTreeFormat4().initialize(control)
656
        return tree
657
658
    def test_invalid_rename(self):
659
        tree = self.create_wt4()
660
        # Create a corrupted dirstate
661
        tree.lock_write()
662
        try:
663
            tree.commit('init') # We need a parent, or we always compare with NULL
664
            state = tree.current_dirstate()
665
            state._read_dirblocks_if_needed()
666
            # Now add in an invalid entry, a rename with a dangling pointer
667
            state._dirblocks[1][1].append((('', 'foo', 'foo-id'),
668
                                            [('f', '', 0, False, ''),
669
                                             ('r', 'bar', 0 , False, '')]))
670
            self.assertListRaises(errors.CorruptDirstate,
671
                                  tree.iter_changes, tree.basis_tree())
672
        finally:
673
            tree.unlock()
674
675
    def get_simple_dirblocks(self, state):
676
        """Extract the simple information from the DirState.
677
678
        This returns the dirblocks, only with the sha1sum and stat details
679
        filtered out.
680
        """
681
        simple_blocks = []
682
        for block in state._dirblocks:
683
            simple_block = (block[0], [])
684
            for entry in block[1]:
685
                # Include the key for each entry, and for each parent include
686
                # just the minikind, so we know if it was
687
                # present/absent/renamed/etc
688
                simple_block[1].append((entry[0], [i[0] for i in entry[1]]))
689
            simple_blocks.append(simple_block)
690
        return simple_blocks
691
692
    def test_update_basis_with_invalid_delta(self):
693
        """When given an invalid delta, it should abort, and not be saved."""
694
        self.build_tree(['dir/', 'dir/file'])
695
        tree = self.create_wt4()
696
        tree.lock_write()
697
        self.addCleanup(tree.unlock)
698
        tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
699
        first_revision_id = tree.commit('init')
700
701
        root_id = tree.path2id('')
702
        state = tree.current_dirstate()
703
        state._read_dirblocks_if_needed()
704
        self.assertEqual([
705
            ('', [(('', '', root_id), ['d', 'd'])]),
706
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
707
            ('dir', [(('dir', 'file', 'file-id'), ['f', 'f'])]),
708
        ],  self.get_simple_dirblocks(state))
709
710
        tree.remove(['dir/file'])
711
        self.assertEqual([
712
            ('', [(('', '', root_id), ['d', 'd'])]),
713
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
714
            ('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
715
        ],  self.get_simple_dirblocks(state))
716
        # Make sure the removal is written to disk
717
        tree.flush()
718
719
        # self.assertRaises(Exception, tree.update_basis_by_delta,
720
        new_dir = inventory.InventoryDirectory('dir-id', 'new-dir', root_id)
721
        new_dir.revision = 'new-revision-id'
722
        new_file = inventory.InventoryFile('file-id', 'new-file', root_id)
723
        new_file.revision = 'new-revision-id'
724
        self.assertRaises(errors.InconsistentDelta,
725
            tree.update_basis_by_delta, 'new-revision-id',
726
            [('dir', 'new-dir', 'dir-id', new_dir),
727
             ('dir/file', 'new-dir/new-file', 'file-id', new_file),
728
            ])
729
        del state
730
731
        # Now when we re-read the file it should not have been modified
732
        tree.unlock()
733
        tree.lock_read()
734
        self.assertEqual(first_revision_id, tree.last_revision())
735
        state = tree.current_dirstate()
736
        state._read_dirblocks_if_needed()
737
        self.assertEqual([
738
            ('', [(('', '', root_id), ['d', 'd'])]),
739
            ('', [(('', 'dir', 'dir-id'), ['d', 'd'])]),
740
            ('dir', [(('dir', 'file', 'file-id'), ['a', 'f'])]),
741
        ],  self.get_simple_dirblocks(state))