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
from cStringIO import StringIO
21
from bzrlib import dirstate, ignores
23
from bzrlib.branch import Branch
24
from bzrlib import bzrdir, conflicts, errors, workingtree
25
from bzrlib.bzrdir import BzrDir
26
from bzrlib.errors import NotBranchError, NotVersionedError
27
from bzrlib.lockdir import LockDir
28
from bzrlib.mutabletree import needs_tree_write_lock
29
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
30
from bzrlib.symbol_versioning import zero_thirteen
31
from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
32
from bzrlib.trace import mutter
33
from bzrlib.transport import get_transport
34
from bzrlib.workingtree import (
42
class TestTreeDirectory(TestCaseWithTransport):
44
def test_kind_character(self):
45
self.assertEqual(TreeDirectory().kind_character(), '/')
48
class TestTreeEntry(TestCaseWithTransport):
50
def test_kind_character(self):
51
self.assertEqual(TreeEntry().kind_character(), '???')
54
class TestTreeFile(TestCaseWithTransport):
56
def test_kind_character(self):
57
self.assertEqual(TreeFile().kind_character(), '')
60
class TestTreeLink(TestCaseWithTransport):
62
def test_kind_character(self):
63
self.assertEqual(TreeLink().kind_character(), '')
66
class TestDefaultFormat(TestCaseWithTransport):
68
def test_get_set_default_format(self):
69
old_format = workingtree.WorkingTreeFormat.get_default_format()
71
self.assertTrue(isinstance(old_format, workingtree.WorkingTreeFormat3))
72
workingtree.WorkingTreeFormat.set_default_format(SampleTreeFormat())
74
# the default branch format is used by the meta dir format
75
# which is not the default bzrdir format at this point
76
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
77
dir.create_repository()
79
result = dir.create_workingtree()
80
self.assertEqual(result, 'A tree')
82
workingtree.WorkingTreeFormat.set_default_format(old_format)
83
self.assertEqual(old_format, workingtree.WorkingTreeFormat.get_default_format())
86
class SampleTreeFormat(workingtree.WorkingTreeFormat):
89
this format is initializable, unsupported to aid in testing the
90
open and open_downlevel routines.
93
def get_format_string(self):
94
"""See WorkingTreeFormat.get_format_string()."""
95
return "Sample tree format."
97
def initialize(self, a_bzrdir, revision_id=None):
98
"""Sample branches cannot be created."""
99
t = a_bzrdir.get_workingtree_transport(self)
100
t.put_bytes('format', self.get_format_string())
103
def is_supported(self):
106
def open(self, transport, _found=False):
107
return "opened tree."
110
class TestWorkingTreeFormat(TestCaseWithTransport):
111
"""Tests for the WorkingTreeFormat facility."""
113
def test_find_format(self):
114
# is the right format object found for a working tree?
115
# create a branch with a few known format objects.
116
self.build_tree(["foo/", "bar/"])
117
def check_format(format, url):
118
dir = format._matchingbzrdir.initialize(url)
119
dir.create_repository()
121
format.initialize(dir)
122
t = get_transport(url)
123
found_format = workingtree.WorkingTreeFormat.find_format(dir)
124
self.failUnless(isinstance(found_format, format.__class__))
125
check_format(workingtree.WorkingTreeFormat3(), "bar")
127
def test_find_format_no_tree(self):
128
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
129
self.assertRaises(errors.NoWorkingTree,
130
workingtree.WorkingTreeFormat.find_format,
133
def test_find_format_unknown_format(self):
134
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
135
dir.create_repository()
137
SampleTreeFormat().initialize(dir)
138
self.assertRaises(errors.UnknownFormatError,
139
workingtree.WorkingTreeFormat.find_format,
142
def test_register_unregister_format(self):
143
format = SampleTreeFormat()
145
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
146
dir.create_repository()
149
format.initialize(dir)
150
# register a format for it.
151
workingtree.WorkingTreeFormat.register_format(format)
152
# which branch.Open will refuse (not supported)
153
self.assertRaises(errors.UnsupportedFormatError, workingtree.WorkingTree.open, '.')
154
# but open_downlevel will work
155
self.assertEqual(format.open(dir), workingtree.WorkingTree.open_downlevel('.'))
156
# unregister the format
157
workingtree.WorkingTreeFormat.unregister_format(format)
160
class TestWorkingTreeFormat3(TestCaseWithTransport):
161
"""Tests specific to WorkingTreeFormat3."""
163
def test_disk_layout(self):
164
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
165
control.create_repository()
166
control.create_branch()
167
tree = workingtree.WorkingTreeFormat3().initialize(control)
169
# format 'Bazaar-NG Working Tree format 3'
170
# inventory = blank inventory
171
# pending-merges = ''
173
# no inventory.basis yet
174
t = control.get_workingtree_transport(None)
175
self.assertEqualDiff('Bazaar-NG Working Tree format 3',
176
t.get('format').read())
177
# self.assertContainsRe(t.get('inventory').read(),
178
# '<inventory file_id="[^"]*" format="5">\n'
181
# WorkingTreeFormat3 doesn't default to creating a unique root id,
182
# because it is incompatible with older bzr versions
183
self.assertContainsRe(t.get('inventory').read(),
184
'<inventory format="5">\n'
187
self.assertEqualDiff('### bzr hashcache v5\n',
188
t.get('stat-cache').read())
189
self.assertFalse(t.has('inventory.basis'))
190
# no last-revision file means 'None' or 'NULLREVISION'
191
self.assertFalse(t.has('last-revision'))
192
# TODO RBC 20060210 do a commit, check the inventory.basis is created
193
# correctly and last-revision file becomes present.
195
def test_uses_lockdir(self):
196
"""WorkingTreeFormat3 uses its own LockDir:
198
- lock is a directory
199
- when the WorkingTree is locked, LockDir can see that
201
t = self.get_transport()
203
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
204
repo = dir.create_repository()
205
branch = dir.create_branch()
207
tree = workingtree.WorkingTreeFormat3().initialize(dir)
208
except errors.NotLocalUrl:
209
raise TestSkipped('Not a local URL')
210
self.assertIsDirectory('.bzr', t)
211
self.assertIsDirectory('.bzr/checkout', t)
212
self.assertIsDirectory('.bzr/checkout/lock', t)
213
our_lock = LockDir(t, '.bzr/checkout/lock')
214
self.assertEquals(our_lock.peek(), None)
216
self.assertTrue(our_lock.peek())
218
self.assertEquals(our_lock.peek(), None)
220
def test_missing_pending_merges(self):
221
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
222
control.create_repository()
223
control.create_branch()
224
tree = workingtree.WorkingTreeFormat3().initialize(control)
225
tree._control_files._transport.delete("pending-merges")
226
self.assertEqual([], tree.get_parent_ids())
229
class TestWorkingTreeFormat4(TestCaseWithTransport):
230
"""Tests specific to WorkingTreeFormat4."""
232
def test_disk_layout(self):
233
control = bzrdir.BzrDir.create(self.get_url())
234
control.create_repository()
235
control.create_branch()
236
tree = workingtree.WorkingTreeFormat4().initialize(control)
238
# format 'Bazaar Working Tree format 4'
239
# inventory = blank inventory
240
# pending-merges = ''
242
# no inventory.basis yet
243
t = control.get_workingtree_transport(None)
244
self.assertEqualDiff('Bazaar Working Tree format 4',
245
t.get('format').read())
246
self.assertEqualDiff('<inventory format="5">\n'
248
t.get('inventory').read())
249
self.assertEqualDiff('### bzr hashcache v5\n',
250
t.get('stat-cache').read())
251
self.assertFalse(t.has('inventory.basis'))
252
# no last-revision file means 'None' or 'NULLREVISION'
253
self.assertFalse(t.has('last-revision'))
254
# TODO RBC 20060210 do a commit, check the inventory.basis is created
255
# correctly and last-revision file becomes present.
256
# manually make a dirstate toc check the format is as desired.
257
state = dirstate.DirState.on_file(t.local_abspath('dirstate'))
258
self.assertEqual([], state.get_parent_ids())
260
def test_uses_lockdir(self):
261
"""WorkingTreeFormat4 uses its own LockDir:
263
- lock is a directory
264
- when the WorkingTree is locked, LockDir can see that
266
# this test could be factored into a subclass of tests common to both
267
# format 3 and 4, but for now its not much of an issue as there is only one in common.
268
t = self.get_transport()
269
tree = self.make_workingtree()
270
self.assertIsDirectory('.bzr', t)
271
self.assertIsDirectory('.bzr/checkout', t)
272
self.assertIsDirectory('.bzr/checkout/lock', t)
273
our_lock = LockDir(t, '.bzr/checkout/lock')
274
self.assertEquals(our_lock.peek(), None)
276
self.assertTrue(our_lock.peek())
278
self.assertEquals(our_lock.peek(), None)
280
def make_workingtree(self):
282
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
283
repo = dir.create_repository()
284
branch = dir.create_branch()
286
return workingtree.WorkingTreeFormat4().initialize(dir)
287
except errors.NotLocalUrl:
288
raise TestSkipped('Not a local URL')
290
# TODO: test that dirstate also stores & retrieves the parent list of
291
# workingtree-parent revisions, including when they have multiple parents.
292
# (in other words, the case when we're constructing a merge of
293
# revisions which are themselves merges.)
295
# The simplest case is that the the workingtree's primary
296
# parent tree can be retrieved. This is required for all WorkingTrees,
297
# and covered by the generic tests.
299
def test_dirstate_stores_all_parent_inventories(self):
300
tree = self.make_workingtree()
302
# We're going to build in tree a working tree
303
# with three parent trees, with some files in common.
305
# We really don't want to do commit or merge in the new dirstate-based
306
# tree, because that might not work yet. So instead we build
307
# revisions elsewhere and pull them across, doing by hand part of the
308
# work that merge would do.
310
subtree = self.make_branch_and_tree('subdir')
311
self.build_tree(['subdir/file-a',])
312
subtree.add(['file-a'], ['id-a'])
313
rev1 = subtree.commit('commit in subdir')
314
rev1_tree = subtree.basis_tree()
316
subtree2 = subtree.bzrdir.sprout('subdir2').open_workingtree()
317
self.build_tree(['subdir2/file-b'])
318
subtree2.add(['file-b'], ['id-b'])
319
rev2 = subtree2.commit('commit in subdir2')
320
rev2_tree = subtree2.basis_tree()
322
subtree.merge_from_branch(subtree2.branch)
323
rev3 = subtree.commit('merge from subdir2')
324
rev3_tree = subtree.basis_tree()
326
repo = tree.branch.repository
327
repo.fetch(subtree.branch.repository, rev3)
328
# will also pull the others...
330
# tree doesn't contain a text merge yet but we'll just
331
# set the parents as if a merge had taken place.
332
# this should cause the tree data to be folded into the
334
## import pdb;pdb.set_trace()
335
tree.set_parent_trees([
338
(rev3, rev3_tree), ])
340
# now we should be able to get them back out
341
self.assertTreesEqual(tree.revision_tree(rev1), rev1_tree)
342
self.assertTreesEqual(tree.revision_tree(rev2), rev2_tree)
343
self.assertTreesEqual(tree.revision_tree(rev3), rev3_tree)
345
def test_dirstate_doesnt_read_parents_from_repo_when_setting(self):
346
"""Setting parent trees on a dirstate working tree takes
347
the trees it's given and doesn't need to read them from the
350
tree = self.make_workingtree()
352
subtree = self.make_branch_and_tree('subdir')
353
rev1 = subtree.commit('commit in subdir')
354
rev1_tree = subtree.basis_tree()
356
tree.branch.pull(subtree.branch)
358
# break the repository's legs to make sure it only uses the trees
359
# it's given; any calls to forbidden methods will raise an
361
repo = tree.branch.repository
362
repo.get_revision = self.fail
363
repo.get_inventory = self.fail
364
repo.get_inventory_xml = self.fail
365
# try to set the parent trees.
366
tree.set_parent_trees([(rev1, rev1_tree)])
368
def test_dirstate_doesnt_read_from_repo_when_returning_cache_tree(self):
369
"""Getting parent trees from a dirstate tree does not read from the
370
repos inventory store. This is an important part of the dirstate
371
performance optimisation work.
373
tree = self.make_workingtree()
375
subtree = self.make_branch_and_tree('subdir')
376
rev1 = subtree.commit('commit in subdir')
377
rev1_tree = subtree.basis_tree()
378
rev2 = subtree.commit('second commit in subdir', allow_pointless=True)
379
rev2_tree = subtree.basis_tree()
381
tree.branch.pull(subtree.branch)
383
# break the repository's legs to make sure it only uses the trees
384
# it's given; any calls to forbidden methods will raise an
386
repo = tree.branch.repository
387
# dont uncomment this: the revision object must be accessed to
388
# answer 'get_parent_ids' for the revision tree- dirstate does not
389
# cache the parents of a parent tree at this point.
390
#repo.get_revision = self.fail
391
repo.get_inventory = self.fail
392
repo.get_inventory_xml = self.fail
393
# set the parent trees.
394
tree.set_parent_trees([(rev1, rev1_tree), (rev2, rev2_tree)])
395
# read the first tree
396
result_rev1_tree = tree.revision_tree(rev1)
398
result_rev2_tree = tree.revision_tree(rev2)
399
# compare - there should be no differences between the handed and
401
self.assertTreesEqual(rev1_tree, result_rev1_tree)
402
self.assertTreesEqual(rev2_tree, result_rev2_tree)
404
def test_dirstate_doesnt_cache_non_parent_trees(self):
405
"""Getting parent trees from a dirstate tree does not read from the
406
repos inventory store. This is an important part of the dirstate
407
performance optimisation work.
409
tree = self.make_workingtree()
411
# make a tree that we can try for, which is able to be returned but
413
subtree = self.make_branch_and_tree('subdir')
414
rev1 = subtree.commit('commit in subdir')
415
tree.branch.pull(subtree.branch)
417
self.assertRaises(errors.NoSuchRevision, tree.revision_tree, rev1)
419
def test_no_dirstate_outside_lock(self):
420
# temporary test until the code is mature enough to test from outside.
421
"""Getting a dirstate object fails if there is no lock."""
422
def lock_and_call_current_dirstate(tree, lock_method):
423
getattr(tree, lock_method)()
424
tree.current_dirstate()
426
tree = self.make_workingtree()
427
self.assertRaises(errors.NotWriteLocked, tree.current_dirstate)
428
lock_and_call_current_dirstate(tree, 'lock_read')
429
self.assertRaises(errors.NotWriteLocked, tree.current_dirstate)
430
lock_and_call_current_dirstate(tree, 'lock_write')
431
self.assertRaises(errors.NotWriteLocked, tree.current_dirstate)
432
lock_and_call_current_dirstate(tree, 'lock_tree_write')
433
self.assertRaises(errors.NotWriteLocked, tree.current_dirstate)
435
def test_new_dirstate_on_new_lock(self):
436
# until we have detection for when a dirstate can be reused, we
437
# want to reparse dirstate on every new lock.
438
known_dirstates = set()
439
def lock_and_compare_all_current_dirstate(tree, lock_method):
440
getattr(tree, lock_method)()
441
state = tree.current_dirstate()
442
self.assertFalse(state in known_dirstates)
443
known_dirstates.add(state)
445
tree = self.make_workingtree()
446
# lock twice with each type to prevent silly per-lock-type bugs.
447
# each lock and compare looks for a unique state object.
448
lock_and_compare_all_current_dirstate(tree, 'lock_read')
449
lock_and_compare_all_current_dirstate(tree, 'lock_read')
450
lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
451
lock_and_compare_all_current_dirstate(tree, 'lock_tree_write')
452
lock_and_compare_all_current_dirstate(tree, 'lock_write')
453
lock_and_compare_all_current_dirstate(tree, 'lock_write')
456
class TestFormat2WorkingTree(TestCaseWithTransport):
457
"""Tests that are specific to format 2 trees."""
459
def create_format2_tree(self, url):
460
return self.make_branch_and_tree(
461
url, format=bzrlib.bzrdir.BzrDirFormat6())
463
def test_conflicts(self):
464
# test backwards compatability
465
tree = self.create_format2_tree('.')
466
self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
468
file('lala.BASE', 'wb').write('labase')
469
expected = conflicts.ContentsConflict('lala')
470
self.assertEqual(list(tree.conflicts()), [expected])
471
file('lala', 'wb').write('la')
472
tree.add('lala', 'lala-id')
473
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
474
self.assertEqual(list(tree.conflicts()), [expected])
475
file('lala.THIS', 'wb').write('lathis')
476
file('lala.OTHER', 'wb').write('laother')
477
# When "text conflict"s happen, stem, THIS and OTHER are text
478
expected = conflicts.TextConflict('lala', file_id='lala-id')
479
self.assertEqual(list(tree.conflicts()), [expected])
480
os.unlink('lala.OTHER')
481
os.mkdir('lala.OTHER')
482
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
483
self.assertEqual(list(tree.conflicts()), [expected])
486
class TestNonFormatSpecificCode(TestCaseWithTransport):
487
"""This class contains tests of workingtree that are not format specific."""
489
def test_gen_file_id(self):
490
file_id = self.applyDeprecated(zero_thirteen, workingtree.gen_file_id,
492
self.assertStartsWith(file_id, 'filename-')
494
def test_gen_root_id(self):
495
file_id = self.applyDeprecated(zero_thirteen, workingtree.gen_root_id)
496
self.assertStartsWith(file_id, 'tree_root-')
499
class InstrumentedTree(object):
500
"""A instrumented tree to check the needs_tree_write_lock decorator."""
505
def lock_tree_write(self):
506
self._locks.append('t')
508
@needs_tree_write_lock
509
def method_with_tree_write_lock(self, *args, **kwargs):
510
"""A lock_tree_write decorated method that returns its arguments."""
513
@needs_tree_write_lock
514
def method_that_raises(self):
515
"""This method causes an exception when called with parameters.
517
This allows the decorator code to be checked - it should still call
522
self._locks.append('u')
525
class TestInstrumentedTree(TestCase):
527
def test_needs_tree_write_lock(self):
528
"""@needs_tree_write_lock should be semantically transparent."""
529
tree = InstrumentedTree()
531
'method_with_tree_write_lock',
532
tree.method_with_tree_write_lock.__name__)
534
"A lock_tree_write decorated method that returns its arguments.",
535
tree.method_with_tree_write_lock.__doc__)
538
result = tree.method_with_tree_write_lock(1,2,3, a='b')
539
self.assertEqual((args, kwargs), result)
540
self.assertEqual(['t', 'u'], tree._locks)
541
self.assertRaises(TypeError, tree.method_that_raises, 'foo')
542
self.assertEqual(['t', 'u', 't', 'u'], tree._locks)