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"""
30
from bzrlib.lockdir import LockDir
31
from bzrlib.tests import TestCaseWithTransport, TestSkipped
32
from bzrlib.tree import InterTree
35
class TestWorkingTreeFormat4(TestCaseWithTransport):
36
"""Tests specific to WorkingTreeFormat4."""
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)
44
# format 'Bazaar Working Tree format 4'
46
t = control.get_workingtree_transport(None)
47
self.assertEqualDiff('Bazaar Working Tree Format 4 (bzr 0.15)\n',
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'))
55
self.assertEqual([], state.get_parent_ids())
59
def test_uses_lockdir(self):
60
"""WorkingTreeFormat4 uses its own LockDir:
63
- when the WorkingTree is locked, LockDir can see that
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)
75
self.assertTrue(our_lock.peek())
77
self.assertEquals(our_lock.peek(), None)
79
def make_workingtree(self, relpath=''):
80
url = self.get_url(relpath)
82
self.build_tree([relpath + '/'])
83
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
84
repo = dir.create_repository()
85
branch = dir.create_branch()
87
return workingtree_4.WorkingTreeFormat4().initialize(dir)
88
except errors.NotLocalUrl:
89
raise TestSkipped('Not a local URL')
91
def test_dirstate_stores_all_parent_inventories(self):
92
tree = self.make_workingtree()
94
# We're going to build in tree a working tree
95
# with three parent trees, with some files in common.
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.
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.
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')
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')
118
subtree.merge_from_branch(subtree2.branch)
119
rev3 = subtree.commit('merge from subdir2')
121
repo = tree.branch.repository
122
repo.fetch(subtree.branch.repository, rev3)
123
# will also pull the others...
125
# create repository based revision trees
126
rev1_revtree = subtree.branch.repository.revision_tree(rev1)
127
rev2_revtree = subtree2.branch.repository.revision_tree(rev2)
128
rev3_revtree = subtree.branch.repository.revision_tree(rev3)
129
# tree doesn't contain a text merge yet but we'll just
130
# set the parents as if a merge had taken place.
131
# this should cause the tree data to be folded into the
133
tree.set_parent_trees([
134
(rev1, rev1_revtree),
135
(rev2, rev2_revtree),
136
(rev3, rev3_revtree), ])
138
# create tree-sourced revision trees
139
rev1_tree = tree.revision_tree(rev1)
140
rev1_tree.lock_read()
141
self.addCleanup(rev1_tree.unlock)
142
rev2_tree = tree.revision_tree(rev2)
143
rev2_tree.lock_read()
144
self.addCleanup(rev2_tree.unlock)
145
rev3_tree = tree.revision_tree(rev3)
146
rev3_tree.lock_read()
147
self.addCleanup(rev3_tree.unlock)
149
# now we should be able to get them back out
150
self.assertTreesEqual(rev1_revtree, rev1_tree)
151
self.assertTreesEqual(rev2_revtree, rev2_tree)
152
self.assertTreesEqual(rev3_revtree, rev3_tree)
154
def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
155
"""Setting parent trees on a dirstate working tree takes
156
the trees it's given and doesn't need to read them from the
159
tree = self.make_workingtree()
161
subtree = self.make_branch_and_tree('subdir')
162
rev1 = subtree.commit('commit in subdir')
163
rev1_tree = subtree.basis_tree()
164
rev1_tree.lock_read()
165
self.addCleanup(rev1_tree.unlock)
167
tree.branch.pull(subtree.branch)
169
# break the repository's legs to make sure it only uses the trees
170
# it's given; any calls to forbidden methods will raise an
172
repo = tree.branch.repository
173
repo.get_revision = self.fail
174
repo.get_inventory = self.fail
175
repo.get_inventory_xml = self.fail
176
# try to set the parent trees.
177
tree.set_parent_trees([(rev1, rev1_tree)])
179
def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
180
"""Getting parent trees from a dirstate tree does not read from the
181
repos inventory store. This is an important part of the dirstate
182
performance optimisation work.
184
tree = self.make_workingtree()
186
subtree = self.make_branch_and_tree('subdir')
187
# writelock the tree so its repository doesn't get readlocked by
188
# the revision tree locks. This works around the bug where we dont
189
# permit lock upgrading.
191
self.addCleanup(subtree.unlock)
192
rev1 = subtree.commit('commit in subdir')
193
rev1_tree = subtree.basis_tree()
194
rev1_tree.lock_read()
196
self.addCleanup(rev1_tree.unlock)
197
rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
198
rev2_tree = subtree.basis_tree()
199
rev2_tree.lock_read()
201
self.addCleanup(rev2_tree.unlock)
203
tree.branch.pull(subtree.branch)
205
# break the repository's legs to make sure it only uses the trees
206
# it's given; any calls to forbidden methods will raise an
208
repo = tree.branch.repository
209
# dont uncomment this: the revision object must be accessed to
210
# answer 'get_parent_ids' for the revision tree- dirstate does not
211
# cache the parents of a parent tree at this point.
212
#repo.get_revision = self.fail
213
repo.get_inventory = self.fail
214
repo.get_inventory_xml = self.fail
215
# set the parent trees.
216
tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
217
# read the first tree
218
result_rev1_tree = tree.revision_tree(rev1)
220
result_rev2_tree = tree.revision_tree(rev2)
221
# compare - there should be no differences between the handed and
223
self.assertTreesEqual(rev1_tree, result_rev1_tree)
224
self.assertTreesEqual(rev2_tree, result_rev2_tree)
226
def test_dirstate_doesnt_cache_non_parent_trees(self):
227
"""Getting parent trees from a dirstate tree does not read from the
228
repos inventory store. This is an important part of the dirstate
229
performance optimisation work.
231
tree = self.make_workingtree()
233
# make a tree that we can try for, which is able to be returned but
235
subtree = self.make_branch_and_tree('subdir')
236
rev1 = subtree.commit('commit in subdir')
237
tree.branch.pull(subtree.branch)
239
self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
241
def test_no_dirstate_outside_lock(self):
242
# temporary test until the code is mature enough to test from outside.
243
"""Getting a dirstate object fails if there is no lock."""
244
def lock_and_call_current_dirstate(tree, lock_method):
245
getattr(tree, lock_method)()
246
tree.current_dirstate()
248
tree = self.make_workingtree()
249
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
250
lock_and_call_current_dirstate(tree, 'lock_read')
251
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
252
lock_and_call_current_dirstate(tree, 'lock_write')
253
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
254
lock_and_call_current_dirstate(tree, 'lock_tree_write')
255
self.assertRaises(errors.ObjectNotLocked, tree.current_dirstate)
257
def test_new_dirstate_on_new_lock(self):
258
# until we have detection for when a dirstate can be reused, we
259
# want to reparse dirstate on every new lock.
260
known_dirstates = set()
261
def lock_and_compare_all_current_dirstate(tree, lock_method):
262
getattr(tree, lock_method)()
263
state = tree.current_dirstate()
264
self.assertFalse(state in known_dirstates)
265
known_dirstates.add(state)
267
tree = self.make_workingtree()
268
# lock twice with each type to prevent silly per-lock-type bugs.
269
# each lock and compare looks for a unique state object.
270
lock_and_compare_all_current_dirstate(tree, 'lock_read')
271
lock_and_compare_all_current_dirstate(tree, 'lock_read')
272
lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
273
lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
274
lock_and_compare_all_current_dirstate(tree, 'lock_write')
275
lock_and_compare_all_current_dirstate(tree, 'lock_write')
277
def test_constructing_invalid_interdirstate_raises(self):
278
tree = self.make_workingtree()
279
rev_id = tree.commit('first post')
280
rev_id2 = tree.commit('second post')
281
rev_tree = tree.branch.repository.revision_tree(rev_id)
282
# Exception is not a great thing to raise, but this test is
283
# very short, and code is used to sanity check other tests, so
284
# a full error object is YAGNI.
286
Exception, workingtree_4.InterDirStateTree, rev_tree, tree)
288
Exception, workingtree_4.InterDirStateTree, tree, rev_tree)
290
def test_revtree_to_revtree_not_interdirstate(self):
291
# we should not get a dirstate optimiser for two repository sourced
292
# revtrees. we can't prove a negative, so we dont do exhaustive tests
293
# of all formats; though that could be written in the future it doesn't
294
# seem well worth it.
295
tree = self.make_workingtree()
296
rev_id = tree.commit('first post')
297
rev_id2 = tree.commit('second post')
298
rev_tree = tree.branch.repository.revision_tree(rev_id)
299
rev_tree2 = tree.branch.repository.revision_tree(rev_id2)
300
optimiser = InterTree.get(rev_tree, rev_tree2)
301
self.assertIsInstance(optimiser, InterTree)
302
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
303
optimiser = InterTree.get(rev_tree2, rev_tree)
304
self.assertIsInstance(optimiser, InterTree)
305
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
307
def test_revtree_not_in_dirstate_to_dirstate_not_interdirstate(self):
308
# we should not get a dirstate optimiser when the revision id for of
309
# the source is not in the dirstate of the target.
310
tree = self.make_workingtree()
311
rev_id = tree.commit('first post')
312
rev_id2 = tree.commit('second post')
313
rev_tree = tree.branch.repository.revision_tree(rev_id)
315
optimiser = InterTree.get(rev_tree, tree)
316
self.assertIsInstance(optimiser, InterTree)
317
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
318
optimiser = InterTree.get(tree, rev_tree)
319
self.assertIsInstance(optimiser, InterTree)
320
self.assertFalse(isinstance(optimiser, workingtree_4.InterDirStateTree))
323
def test_empty_basis_to_dirstate_tree(self):
324
# we should get a InterDirStateTree for doing
325
# 'changes_from' from the first basis dirstate revision tree to a
327
tree = self.make_workingtree()
329
basis_tree = tree.basis_tree()
330
basis_tree.lock_read()
331
optimiser = InterTree.get(basis_tree, tree)
334
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
336
def test_nonempty_basis_to_dirstate_tree(self):
337
# we should get a InterDirStateTree for doing
338
# 'changes_from' from a non-null basis dirstate revision tree to a
340
tree = self.make_workingtree()
341
tree.commit('first post')
343
basis_tree = tree.basis_tree()
344
basis_tree.lock_read()
345
optimiser = InterTree.get(basis_tree, tree)
348
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
350
def test_empty_basis_revtree_to_dirstate_tree(self):
351
# we should get a InterDirStateTree for doing
352
# 'changes_from' from an empty repository based rev tree to a
354
tree = self.make_workingtree()
356
basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
357
basis_tree.lock_read()
358
optimiser = InterTree.get(basis_tree, tree)
361
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
363
def test_nonempty_basis_revtree_to_dirstate_tree(self):
364
# we should get a InterDirStateTree for doing
365
# 'changes_from' from a non-null repository based rev tree to a
367
tree = self.make_workingtree()
368
tree.commit('first post')
370
basis_tree = tree.branch.repository.revision_tree(tree.last_revision())
371
basis_tree.lock_read()
372
optimiser = InterTree.get(basis_tree, tree)
375
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
377
def test_tree_to_basis_in_other_tree(self):
378
# we should get a InterDirStateTree when
379
# the source revid is in the dirstate object of the target and
380
# the dirstates are different. This is largely covered by testing
381
# with repository revtrees, so is just for extra confidence.
382
tree = self.make_workingtree('a')
383
tree.commit('first post')
384
tree2 = self.make_workingtree('b')
385
tree2.pull(tree.branch)
386
basis_tree = tree.basis_tree()
388
basis_tree.lock_read()
389
optimiser = InterTree.get(basis_tree, tree2)
392
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
394
def test_merged_revtree_to_tree(self):
395
# we should get a InterDirStateTree when
396
# the source tree is a merged tree present in the dirstate of target.
397
tree = self.make_workingtree('a')
398
tree.commit('first post')
399
tree.commit('tree 1 commit 2')
400
tree2 = self.make_workingtree('b')
401
tree2.pull(tree.branch)
402
tree2.commit('tree 2 commit 2')
403
tree.merge_from_branch(tree2.branch)
404
second_parent_tree = tree.revision_tree(tree.get_parent_ids()[1])
405
second_parent_tree.lock_read()
407
optimiser = InterTree.get(second_parent_tree, tree)
409
second_parent_tree.unlock()
410
self.assertIsInstance(optimiser, workingtree_4.InterDirStateTree)
412
def test_id2path(self):
413
tree = self.make_workingtree('tree')
414
self.build_tree(['tree/a', 'tree/b'])
415
tree.add(['a'], ['a-id'])
416
self.assertEqual(u'a', tree.id2path('a-id'))
417
self.assertRaises(errors.NoSuchId, tree.id2path, 'a')
419
tree.add(['b'], ['b-id'])
422
tree.rename_one('a', u'b\xb5rry')
423
new_path = u'b\xb5rry'
424
except UnicodeEncodeError:
425
# support running the test on non-unicode platforms
426
tree.rename_one('a', 'c')
428
self.assertEqual(new_path, tree.id2path('a-id'))
429
tree.commit(u'b\xb5rry')
430
tree.unversion(['a-id'])
431
self.assertRaises(errors.NoSuchId, tree.id2path, 'a-id')
432
self.assertEqual('b', tree.id2path('b-id'))
433
self.assertRaises(errors.NoSuchId, tree.id2path, 'c-id')
435
def test_unique_root_id_per_tree(self):
436
# each time you initialize a new tree, it gets a different root id
437
format_name = 'dirstate-with-subtree'
438
tree1 = self.make_branch_and_tree('tree1',
440
tree2 = self.make_branch_and_tree('tree2',
442
self.assertNotEqual(tree1.get_root_id(), tree2.get_root_id())
443
# when you branch, it inherits the same root id
444
rev1 = tree1.commit('first post')
445
tree3 = tree1.bzrdir.sprout('tree3').open_workingtree()
446
self.assertEqual(tree3.get_root_id(), tree1.get_root_id())
448
def test_set_root_id(self):
449
# similar to some code that fails in the dirstate-plus-subtree branch
450
# -- setting the root id while adding a parent seems to scramble the
451
# dirstate invariants. -- mbp 20070303
455
wt.current_dirstate()._validate()
458
wt = self.make_workingtree('tree')
459
wt.set_root_id('TREE-ROOTID')
461
wt.commit('somenthing')
463
# now switch and commit again
464
wt.set_root_id('tree-rootid')
469
def test_default_root_id(self):
470
tree = self.make_branch_and_tree('tag', format='dirstate-tags')
471
self.assertEqual(inventory.ROOT_ID, tree.get_root_id())
472
tree = self.make_branch_and_tree('subtree',
473
format='dirstate-with-subtree')
474
self.assertNotEqual(inventory.ROOT_ID, tree.get_root_id())
476
def test_non_subtree_with_nested_trees(self):
477
# prior to dirstate, st/diff/commit ignored nested trees.
478
# dirstate, as opposed to dirstate-with-subtree, should
479
# behave the same way.
480
tree = self.make_branch_and_tree('.', format='dirstate')
481
self.assertFalse(tree.supports_tree_reference())
482
self.build_tree(['dir/'])
483
# for testing easily.
484
tree.set_root_id('root')
485
tree.add(['dir'], ['dir-id'])
486
subtree = self.make_branch_and_tree('dir')
487
# the most primitive operation: kind
488
self.assertEqual('directory', tree.kind('dir-id'))
489
# a diff against the basis should give us a directory
491
expected = [('dir-id',
499
self.assertEqual(expected, list(tree._iter_changes(tree.basis_tree(),
500
specific_files=['dir'])))
502
# do a commit, we want to trigger the dirstate fast-path too
503
tree.commit('first post')
504
# change the path for the subdir, which will trigger getting all
506
os.rename('dir', 'also-dir')
507
# now the diff will use the fast path
509
expected = [('dir-id',
517
self.assertEqual(expected, list(tree._iter_changes(tree.basis_tree())))
520
def test_with_subtree_supports_tree_references(self):
521
# dirstate-with-subtree should support tree-references.
522
tree = self.make_branch_and_tree('.', format='dirstate-with-subtree')
523
self.assertTrue(tree.supports_tree_reference())
524
# having checked this is on, the tree interface, and intertree
525
# interface tests, will proceed to test the subtree support of
528
def test_iter_changes_ignores_unversioned_dirs(self):
529
"""_iter_changes should not descend into unversioned directories."""
530
tree = self.make_branch_and_tree('.', format='dirstate')
531
# We have an unversioned directory at the root, a versioned one with
532
# other versioned files and an unversioned directory, and another
533
# versioned dir with nothing but an unversioned directory.
534
self.build_tree(['unversioned/',
538
'versioned/unversioned/',
539
'versioned/unversioned/a',
540
'versioned/unversioned/b/',
543
'versioned2/unversioned/',
544
'versioned2/unversioned/a',
545
'versioned2/unversioned/b/',
547
tree.add(['versioned', 'versioned2', 'versioned2/a'])
548
tree.commit('one', rev_id='rev-1')
549
# Trap osutils._walkdirs_utf8 to spy on what dirs have been accessed.
551
orig_walkdirs = osutils._walkdirs_utf8
553
osutils._walkdirs_utf8 = orig_walkdirs
554
self.addCleanup(reset)
555
def walkdirs_spy(*args, **kwargs):
556
for val in orig_walkdirs(*args, **kwargs):
557
returned.append(val[0][0])
559
osutils._walkdirs_utf8 = walkdirs_spy
561
basis = tree.basis_tree()
563
self.addCleanup(tree.unlock)
565
self.addCleanup(basis.unlock)
566
changes = [c[1] for c in
567
tree._iter_changes(basis, want_unversioned=True)]
568
self.assertEqual([(None, 'unversioned'),
569
(None, 'versioned/unversioned'),
570
(None, 'versioned2/unversioned'),
572
self.assertEqual(['', 'versioned', 'versioned2'], returned)
573
del returned[:] # reset
574
changes = [c[1] for c in tree._iter_changes(basis)]
575
self.assertEqual([], changes)
576
self.assertEqual(['', 'versioned', 'versioned2'], returned)