1
# Copyright (C) 2005, 2006 Canonical Ltd
2
# Authors: Robert Collins <robert.collins@canonical.com>
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.
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.
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
18
"""Tests for WorkingTreeFormat4"""
28
from bzrlib.lockdir import LockDir
29
from bzrlib.tests import TestCaseWithTransport, TestSkipped
30
from bzrlib.tree import InterTree
33
class TestWorkingTreeFormat4(TestCaseWithTransport):
34
"""Tests specific to WorkingTreeFormat4."""
36
def test_disk_layout(self):
37
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
38
control.create_repository()
39
control.create_branch()
40
tree = workingtree_4.WorkingTreeFormat4().initialize(control)
42
# format 'Bazaar Working Tree format 4'
44
t = control.get_workingtree_transport(None)
45
self.assertEqualDiff('Bazaar Working Tree Format 4 (bzr 0.15)\n',
46
t.get('format').read())
47
self.assertFalse(t.has('inventory.basis'))
48
# no last-revision file means 'None' or 'NULLREVISION'
49
self.assertFalse(t.has('last-revision'))
50
state = dirstate.DirState.on_file(t.local_abspath('dirstate'))
53
self.assertEqual([], state.get_parent_ids())
57
def test_uses_lockdir(self):
58
"""WorkingTreeFormat4 uses its own LockDir:
61
- when the WorkingTree is locked, LockDir can see that
63
# this test could be factored into a subclass of tests common to both
64
# format 3 and 4, but for now its not much of an issue as there is only one in common.
65
t = self.get_transport()
66
tree = self.make_workingtree()
67
self.assertIsDirectory('.bzr', t)
68
self.assertIsDirectory('.bzr/checkout', t)
69
self.assertIsDirectory('.bzr/checkout/lock', t)
70
our_lock = LockDir(t, '.bzr/checkout/lock')
71
self.assertEquals(our_lock.peek(), None)
73
self.assertTrue(our_lock.peek())
75
self.assertEquals(our_lock.peek(), None)
77
def make_workingtree(self, relpath=''):
78
url = self.get_url(relpath)
80
self.build_tree([relpath + '/'])
81
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
82
repo = dir.create_repository()
83
branch = dir.create_branch()
85
return workingtree_4.WorkingTreeFormat4().initialize(dir)
86
except errors.NotLocalUrl:
87
raise TestSkipped('Not a local URL')
89
def test_dirstate_stores_all_parent_inventories(self):
90
tree = self.make_workingtree()
92
# We're going to build in tree a working tree
93
# with three parent trees, with some files in common.
95
# We really don't want to do commit or merge in the new dirstate-based
96
# tree, because that might not work yet. So instead we build
97
# revisions elsewhere and pull them across, doing by hand part of the
98
# work that merge would do.
100
subtree = self.make_branch_and_tree('subdir')
101
# writelock the tree so its repository doesn't get readlocked by
102
# the revision tree locks. This works around the bug where we dont
103
# permit lock upgrading.
105
self.addCleanup(subtree.unlock)
106
self.build_tree(['subdir/file-a',])
107
subtree.add(['file-a'], ['id-a'])
108
rev1 = subtree.commit('commit in subdir')
110
subtree2 = subtree.bzrdir.sprout('subdir2').open_workingtree()
111
self.build_tree(['subdir2/file-b'])
112
subtree2.add(['file-b'], ['id-b'])
113
rev2 = subtree2.commit('commit in subdir2')
116
subtree.merge_from_branch(subtree2.branch)
117
rev3 = subtree.commit('merge from subdir2')
119
repo = tree.branch.repository
120
repo.fetch(subtree.branch.repository, rev3)
121
# will also pull the others...
123
# create repository based revision trees
124
rev1_revtree = subtree.branch.repository.revision_tree(rev1)
125
rev2_revtree = subtree2.branch.repository.revision_tree(rev2)
126
rev3_revtree = subtree.branch.repository.revision_tree(rev3)
127
# tree doesn't contain a text merge yet but we'll just
128
# set the parents as if a merge had taken place.
129
# this should cause the tree data to be folded into the
131
tree.set_parent_trees([
132
(rev1, rev1_revtree),
133
(rev2, rev2_revtree),
134
(rev3, rev3_revtree), ])
136
# create tree-sourced revision trees
137
rev1_tree = tree.revision_tree(rev1)
138
rev1_tree.lock_read()
139
self.addCleanup(rev1_tree.unlock)
140
rev2_tree = tree.revision_tree(rev2)
141
rev2_tree.lock_read()
142
self.addCleanup(rev2_tree.unlock)
143
rev3_tree = tree.revision_tree(rev3)
144
rev3_tree.lock_read()
145
self.addCleanup(rev3_tree.unlock)
147
# now we should be able to get them back out
148
self.assertTreesEqual(rev1_revtree, rev1_tree)
149
self.assertTreesEqual(rev2_revtree, rev2_tree)
150
self.assertTreesEqual(rev3_revtree, rev3_tree)
152
def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
153
"""Setting parent trees on a dirstate working tree takes
154
the trees it's given and doesn't need to read them from the
157
tree = self.make_workingtree()
159
subtree = self.make_branch_and_tree('subdir')
160
rev1 = subtree.commit('commit in subdir')
161
rev1_tree = subtree.basis_tree()
162
rev1_tree.lock_read()
163
self.addCleanup(rev1_tree.unlock)
165
tree.branch.pull(subtree.branch)
167
# break the repository's legs to make sure it only uses the trees
168
# it's given; any calls to forbidden methods will raise an
170
repo = tree.branch.repository
171
repo.get_revision = self.fail
172
repo.get_inventory = self.fail
173
repo.get_inventory_xml = self.fail
174
# try to set the parent trees.
175
tree.set_parent_trees([(rev1, rev1_tree)])
177
def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
178
"""Getting parent trees from a dirstate tree does not read from the
179
repos inventory store. This is an important part of the dirstate
180
performance optimisation work.
182
tree = self.make_workingtree()
184
subtree = self.make_branch_and_tree('subdir')
185
# writelock the tree so its repository doesn't get readlocked by
186
# the revision tree locks. This works around the bug where we dont
187
# permit lock upgrading.
189
self.addCleanup(subtree.unlock)
190
rev1 = subtree.commit('commit in subdir')
191
rev1_tree = subtree.basis_tree()
192
rev1_tree.lock_read()
194
self.addCleanup(rev1_tree.unlock)
195
rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
196
rev2_tree = subtree.basis_tree()
197
rev2_tree.lock_read()
199
self.addCleanup(rev2_tree.unlock)
201
tree.branch.pull(subtree.branch)
203
# break the repository's legs to make sure it only uses the trees
204
# it's given; any calls to forbidden methods will raise an
206
repo = tree.branch.repository
207
# dont uncomment this: the revision object must be accessed to
208
# answer 'get_parent_ids' for the revision tree- dirstate does not
209
# cache the parents of a parent tree at this point.
210
#repo.get_revision = self.fail
211
repo.get_inventory = self.fail
212
repo.get_inventory_xml = self.fail
213
# set the parent trees.
214
tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
215
# read the first tree
216
result_rev1_tree = tree.revision_tree(rev1)
218
result_rev2_tree = tree.revision_tree(rev2)
219
# compare - there should be no differences between the handed and
221
self.assertTreesEqual(rev1_tree, result_rev1_tree)
222
self.assertTreesEqual(rev2_tree, result_rev2_tree)
224
def test_dirstate_doesnt_cache_non_parent_trees(self):
225
"""Getting parent trees from a dirstate tree does not read from the
226
repos inventory store. This is an important part of the dirstate
227
performance optimisation work.
229
tree = self.make_workingtree()
231
# make a tree that we can try for, which is able to be returned but
233
subtree = self.make_branch_and_tree('subdir')
234
rev1 = subtree.commit('commit in subdir')
235
tree.branch.pull(subtree.branch)
237
self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
239
def test_no_dirstate_outside_lock(self):
240
# temporary test until the code is mature enough to test from outside.
241
"""Getting a dirstate object fails if there is no lock."""
242
def lock_and_call_current_dirstate(tree, lock_method):
243
getattr(tree, lock_method)()
244
tree.current_dirstate()
246
tree = self.make_workingtree()
247
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
248
lock_and_call_current_dirstate(tree, 'lock_read')
249
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
250
lock_and_call_current_dirstate(tree, 'lock_write')
251
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
252
lock_and_call_current_dirstate(tree, 'lock_tree_write')
253
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
255
def test_new_dirstate_on_new_lock(self):
256
# until we have detection for when a dirstate can be reused, we
257
# want to reparse dirstate on every new lock.
258
known_dirstates = set()
259
def lock_and_compare_all_current_dirstate(tree, lock_method):
260
getattr(tree, lock_method)()
261
state = tree.current_dirstate()
262
self.assertFalse(state in known_dirstates)
263
known_dirstates.add(state)
265
tree = self.make_workingtree()
266
# lock twice with each type to prevent silly per-lock-type bugs.
267
# each lock and compare looks for a unique state object.
268
lock_and_compare_all_current_dirstate(tree, 'lock_read')
269
lock_and_compare_all_current_dirstate(tree, 'lock_read')
270
lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
271
lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
272
lock_and_compare_all_current_dirstate(tree, 'lock_write')
273
lock_and_compare_all_current_dirstate(tree, 'lock_write')
275
def test_constructing_invalid_interdirstate_raises(self):
276
tree = self.make_workingtree()
277
rev_id = tree.commit('first post')
278
rev_id2 = tree.commit('second post')
279
rev_tree = tree.branch.repository.revision_tree(rev_id)
280
# Exception is not a great thing to raise, but this test is
281
# very short, and code is used to sanity check other tests, so
282
# a full error object is YAGNI.
284
Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
286
Exception, workingtree_4.InterDirStateTree, tree, rev_tree)
288
def test_revtree_to_revtree_not_interdirstate(self):
289
# we should not get a dirstate optimiser for two repository sourced
290
# revtrees. we can't prove a negative, so we dont do exhaustive tests
291
# of all formats; though that could be written in the future it doesn't
292
# seem well worth it.
293
tree = self.make_workingtree()
294
rev_id = tree.commit('first post')
295
rev_id2 = tree.commit('second post')
296
rev_tree = tree.branch.repository.revision_tree(rev_id)
297
rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
298
optimiser = InterTree.get(rev_tree, rev_tree2)
299
self.assertIsInstance(optimiser, InterTree)
300
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
301
optimiser = InterTree.get(rev_tree2, rev_tree)
302
self.assertIsInstance(optimiser, InterTree)
303
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
305
def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
306
# we should not get a dirstate optimiser when the revision id for of
307
# the source is not in the dirstate of the target.
308
tree = self.make_workingtree()
309
rev_id = tree.commit('first post')
310
rev_id2 = tree.commit('second post')
311
rev_tree = tree.branch.repository.revision_tree(rev_id)
313
optimiser = InterTree.get(rev_tree, tree)
314
self.assertIsInstance(optimiser, InterTree)
315
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
316
optimiser = InterTree.get(tree, rev_tree)
317
self.assertIsInstance(optimiser, InterTree)
318
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
321
def test_empty_basis_to_dirstate_tree(self):
322
# we should get a InterDirStateTree for doing
323
# 'changes_from' from the first basis dirstate revision tree to a
325
tree = self.make_workingtree()
327
basis_tree = tree.basis_tree()
328
basis_tree.lock_read()
329
optimiser = InterTree.get(basis_tree, tree)
332
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
334
def test_nonempty_basis_to_dirstate_tree(self):
335
# we should get a InterDirStateTree for doing
336
# 'changes_from' from a non-null basis dirstate revision tree to a
338
tree = self.make_workingtree()
339
tree.commit('first post')
341
basis_tree = tree.basis_tree()
342
basis_tree.lock_read()
343
optimiser = InterTree.get(basis_tree, tree)
346
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
348
def test_empty_basis_revtree_to_dirstate_tree(self):
349
# we should get a InterDirStateTree for doing
350
# 'changes_from' from an empty repository based rev tree to a
352
tree = self.make_workingtree()
354
basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
355
basis_tree.lock_read()
356
optimiser = InterTree.get(basis_tree, tree)
359
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
361
def test_nonempty_basis_revtree_to_dirstate_tree(self):
362
# we should get a InterDirStateTree for doing
363
# 'changes_from' from a non-null repository based rev tree to a
365
tree = self.make_workingtree()
366
tree.commit('first post')
368
basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
369
basis_tree.lock_read()
370
optimiser = InterTree.get(basis_tree, tree)
373
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
375
def test_tree_to_basis_in_other_tree(self):
376
# we should get a InterDirStateTree when
377
# the source revid is in the dirstate object of the target and
378
# the dirstates are different. This is largely covered by testing
379
# with repository revtrees, so is just for extra confidence.
380
tree = self.make_workingtree('a')
381
tree.commit('first post')
382
tree2 = self.make_workingtree('b')
383
tree2.pull(tree.branch)
384
basis_tree = tree.basis_tree()
386
basis_tree.lock_read()
387
optimiser = InterTree.get(basis_tree, tree2)
390
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
392
def test_merged_revtree_to_tree(self):
393
# we should get a InterDirStateTree when
394
# the source tree is a merged tree present in the dirstate of target.
395
tree = self.make_workingtree('a')
396
tree.commit('first post')
397
tree.commit('tree 1 commit 2')
398
tree2 = self.make_workingtree('b')
399
tree2.pull(tree.branch)
400
tree2.commit('tree 2 commit 2')
401
tree.merge_from_branch(tree2.branch)
402
second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
403
second_parent_tree.lock_read()
405
optimiser = InterTree.get(second_parent_tree, tree)
407
second_parent_tree.unlock()
408
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
410
def test_id2path(self):
411
tree = self.make_workingtree('tree')
412
self.build_tree(['tree/a', 'tree/b'])
413
tree.add(['a'], ['a-id'])
414
self.assertEqual(u'a', tree.id2path('a-id'))
415
self.assertRaises(errors.NoSuchId, tree.id2path, 'a')
417
tree.add(['b'], ['b-id'])
420
tree.rename_one('a', u'b\xb5rry')
421
new_path = u'b\xb5rry'
422
except UnicodeEncodeError:
423
# support running the test on non-unicode platforms
424
tree.rename_one('a', 'c')
426
self.assertEqual(new_path, tree.id2path('a-id'))
427
tree.commit(u'b\xb5rry')
428
tree.unversion(['a-id'])
429
self.assertRaises(errors.NoSuchId, tree.id2path, 'a-id')
430
self.assertEqual('b', tree.id2path('b-id'))
431
self.assertRaises(errors.NoSuchId, tree.id2path, 'c-id')
433
def test_unique_root_id_per_tree(self):
434
# each time you initialize a new tree, it gets a different root id
435
format_name = 'dirstate-with-subtree'
436
tree1 = self.make_branch_and_tree('tree1',
438
tree2 = self.make_branch_and_tree('tree2',
440
self.assertNotEqual(tree1.get_root_id(), tree2.get_root_id())
441
# when you branch, it inherits the same root id
442
rev1 = tree1.commit('first post')
443
tree3 = tree1.bzrdir.sprout('tree3').open_workingtree()
444
self.assertEqual(tree3.get_root_id(), tree1.get_root_id())
446
def test_set_root_id(self):
447
# similar to some code that fails in the dirstate-plus-subtree branch
448
# -- setting the root id while adding a parent seems to scramble the
449
# dirstate invariants. -- mbp 20070303
453
wt.current_dirstate()._validate()
456
wt = self.make_workingtree('tree')
457
wt.set_root_id('TREE-ROOTID')
459
wt.commit('somenthing')
461
# now switch and commit again
462
wt.set_root_id('tree-rootid')
467
def test_non_subtree_with_nested_trees(self):
468
# prior to dirstate, st/diff/commit ignored nested trees.
469
# dirstate, as opposed to dirstate-with-subtree, should
470
# behave the same way.
471
tree = self.make_branch_and_tree('.', format='dirstate')
472
self.assertFalse(tree.supports_tree_reference())
473
self.build_tree(['dir/'])
474
# for testing easily.
475
tree.set_root_id('root')
476
tree.add(['dir'], ['dir-id'])
477
subtree = self.make_branch_and_tree('dir')
478
# the most primitive operation: kind
479
self.assertEqual('directory', tree.kind('dir-id'))
480
# a diff against the basis should give us a directory
482
expected = [('dir-id',
490
self.assertEqual(expected, list(tree._iter_changes(tree.basis_tree(),
491
specific_files=['dir'])))
493
# do a commit, we want to trigger the dirstate fast-path too
494
tree.commit('first post')
495
# change the path for the subdir, which will trigger getting all
497
os.rename('dir', 'also-dir')
498
# now the diff will use the fast path
500
expected = [('dir-id',
508
self.assertEqual(expected, list(tree._iter_changes(tree.basis_tree())))
511
def test_with_subtree_supports_tree_references(self):
512
# dirstate-with-subtree should support tree-references.
513
tree = self.make_branch_and_tree('.', format='dirstate-with-subtree')
514
self.assertTrue(tree.supports_tree_reference())
515
# having checked this is on, the tree interface, and intertree
516
# interface tests, will proceed to test the subtree support of