14
14
# You should have received a copy of the GNU General Public License
15
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
19
from bzrlib.branch import Branch
20
from bzrlib.errors import NotBranchError, NotVersionedError
21
from bzrlib.tests import TestCaseInTempDir
22
from bzrlib.trace import mutter
23
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
24
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
27
class TestTreeDirectory(TestCaseInTempDir):
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27
from bzrlib.lock import write_locked
28
from bzrlib.lockdir import LockDir
29
from bzrlib.mutabletree import needs_tree_write_lock
30
from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
31
from bzrlib.workingtree import (
39
class TestTreeDirectory(TestCaseWithTransport):
29
41
def test_kind_character(self):
30
42
self.assertEqual(TreeDirectory().kind_character(), '/')
33
class TestTreeEntry(TestCaseInTempDir):
45
class TestTreeEntry(TestCaseWithTransport):
35
47
def test_kind_character(self):
36
48
self.assertEqual(TreeEntry().kind_character(), '???')
39
class TestTreeFile(TestCaseInTempDir):
51
class TestTreeFile(TestCaseWithTransport):
41
53
def test_kind_character(self):
42
54
self.assertEqual(TreeFile().kind_character(), '')
45
class TestTreeLink(TestCaseInTempDir):
57
class TestTreeLink(TestCaseWithTransport):
47
59
def test_kind_character(self):
48
60
self.assertEqual(TreeLink().kind_character(), '')
51
class TestWorkingTree(TestCaseInTempDir):
53
def test_listfiles(self):
54
branch = Branch.initialize(u'.')
56
print >> open('file', 'w'), "content"
58
os.symlink('target', 'symlink')
59
tree = branch.working_tree()
60
files = list(tree.list_files())
61
self.assertEqual(files[0], ('dir', '?', 'directory', None, TreeDirectory()))
62
self.assertEqual(files[1], ('file', '?', 'file', None, TreeFile()))
64
self.assertEqual(files[2], ('symlink', '?', 'symlink', None, TreeLink()))
63
class TestDefaultFormat(TestCaseWithTransport):
65
def test_get_set_default_format(self):
66
old_format = workingtree.format_registry.get_default()
68
self.assertTrue(isinstance(old_format, workingtree_4.WorkingTreeFormat6))
69
workingtree.format_registry.set_default(SampleTreeFormat())
71
# the default branch format is used by the meta dir format
72
# which is not the default bzrdir format at this point
73
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
74
dir.create_repository()
76
result = dir.create_workingtree()
77
self.assertEqual(result, 'A tree')
79
workingtree.format_registry.set_default(old_format)
80
self.assertEqual(old_format, workingtree.format_registry.get_default())
82
def test_from_string(self):
83
self.assertIsInstance(
84
SampleTreeFormat.from_string("Sample tree format."),
86
self.assertRaises(AssertionError,
87
SampleTreeFormat.from_string, "Different format string.")
89
def test_get_set_default_format_by_key(self):
90
old_format = workingtree.format_registry.get_default()
92
format = SampleTreeFormat()
93
workingtree.format_registry.register(format)
94
self.addCleanup(workingtree.format_registry.remove, format)
95
self.assertTrue(isinstance(old_format, workingtree_4.WorkingTreeFormat6))
96
workingtree.format_registry.set_default_key(format.get_format_string())
98
# the default branch format is used by the meta dir format
99
# which is not the default bzrdir format at this point
100
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
101
dir.create_repository()
103
result = dir.create_workingtree()
104
self.assertEqual(result, 'A tree')
106
workingtree.format_registry.set_default_key(
107
old_format.get_format_string())
108
self.assertEqual(old_format, workingtree.format_registry.get_default())
111
tree = self.make_branch_and_tree('.')
112
open_direct = workingtree.WorkingTree.open('.')
113
self.assertEqual(tree.basedir, open_direct.basedir)
114
open_no_args = workingtree.WorkingTree.open()
115
self.assertEqual(tree.basedir, open_no_args.basedir)
66
117
def test_open_containing(self):
67
branch = Branch.initialize(u'.')
68
wt, relpath = WorkingTree.open_containing()
69
self.assertEqual('', relpath)
70
self.assertEqual(wt.basedir, branch.base)
71
wt, relpath = WorkingTree.open_containing(u'.')
72
self.assertEqual('', relpath)
73
self.assertEqual(wt.basedir, branch.base)
74
wt, relpath = WorkingTree.open_containing('./foo')
75
self.assertEqual('foo', relpath)
76
self.assertEqual(wt.basedir, branch.base)
77
# paths that are urls are just plain wrong for working trees.
78
self.assertRaises(NotBranchError,
79
WorkingTree.open_containing,
80
'file:///' + getcwd())
82
def test_construct_with_branch(self):
83
branch = Branch.initialize(u'.')
84
tree = WorkingTree(branch.base, branch)
85
self.assertEqual(branch, tree.branch)
86
self.assertEqual(branch.base, tree.basedir)
88
def test_construct_without_branch(self):
89
branch = Branch.initialize(u'.')
90
tree = WorkingTree(branch.base)
91
self.assertEqual(branch.base, tree.branch.base)
92
self.assertEqual(branch.base, tree.basedir)
94
def test_basic_relpath(self):
95
# for comprehensive relpath tests, see whitebox.py.
96
branch = Branch.initialize(u'.')
97
tree = WorkingTree(branch.base)
98
self.assertEqual('child',
99
tree.relpath(pathjoin(getcwd(), 'child')))
101
def test_lock_locks_branch(self):
102
branch = Branch.initialize(u'.')
103
tree = WorkingTree(branch.base)
105
self.assertEqual(1, tree.branch.control_files._lock_count)
106
self.assertEqual('r', tree.branch.control_files._lock_mode)
108
self.assertEqual(None, tree.branch.control_files._lock_count)
118
tree = self.make_branch_and_tree('.')
119
open_direct, relpath = workingtree.WorkingTree.open_containing('.')
120
self.assertEqual(tree.basedir, open_direct.basedir)
121
self.assertEqual('', relpath)
122
open_no_args, relpath = workingtree.WorkingTree.open_containing()
123
self.assertEqual(tree.basedir, open_no_args.basedir)
124
self.assertEqual('', relpath)
125
open_subdir, relpath = workingtree.WorkingTree.open_containing('subdir')
126
self.assertEqual(tree.basedir, open_subdir.basedir)
127
self.assertEqual('subdir', relpath)
130
class SampleTreeFormat(workingtree.WorkingTreeFormatMetaDir):
133
this format is initializable, unsupported to aid in testing the
134
open and open_downlevel routines.
138
def get_format_string(cls):
139
"""See WorkingTreeFormat.get_format_string()."""
140
return "Sample tree format."
142
def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
143
accelerator_tree=None, hardlink=False):
144
"""Sample branches cannot be created."""
145
t = a_bzrdir.get_workingtree_transport(self)
146
t.put_bytes('format', self.get_format_string())
149
def is_supported(self):
152
def open(self, transport, _found=False):
153
return "opened tree."
156
class SampleExtraTreeFormat(workingtree.WorkingTreeFormat):
157
"""A sample format that does not support use in a metadir.
161
def get_format_string(self):
162
# Not usable in a metadir, so no format string
165
def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
166
accelerator_tree=None, hardlink=False):
167
raise NotImplementedError(self.initialize)
169
def is_supported(self):
172
def open(self, transport, _found=False):
173
raise NotImplementedError(self.open)
176
class TestWorkingTreeFormat(TestCaseWithTransport):
177
"""Tests for the WorkingTreeFormat facility."""
179
def test_find_format_string(self):
180
# is the right format object found for a working tree?
181
branch = self.make_branch('branch')
182
self.assertRaises(errors.NoWorkingTree,
183
workingtree.WorkingTreeFormatMetaDir.find_format_string, branch.bzrdir)
184
transport = branch.bzrdir.get_workingtree_transport(None)
186
transport.put_bytes("format", "some format name")
187
# The format does not have to be known by Bazaar,
188
# find_format_string just retrieves the name
189
self.assertEquals("some format name",
190
workingtree.WorkingTreeFormatMetaDir.find_format_string(branch.bzrdir))
192
def test_find_format(self):
193
# is the right format object found for a working tree?
194
# create a branch with a few known format objects.
195
self.build_tree(["foo/", "bar/"])
196
def check_format(format, url):
197
dir = format._matchingbzrdir.initialize(url)
198
dir.create_repository()
200
format.initialize(dir)
201
t = transport.get_transport(url)
202
found_format = workingtree.WorkingTreeFormatMetaDir.find_format(dir)
203
self.assertIsInstance(found_format, format.__class__)
204
check_format(workingtree_3.WorkingTreeFormat3(), "bar")
206
def test_find_format_no_tree(self):
207
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
208
self.assertRaises(errors.NoWorkingTree,
209
workingtree.WorkingTreeFormatMetaDir.find_format,
212
def test_find_format_unknown_format(self):
213
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
214
dir.create_repository()
216
SampleTreeFormat().initialize(dir)
217
self.assertRaises(errors.UnknownFormatError,
218
workingtree.WorkingTreeFormatMetaDir.find_format,
221
def test_find_format_with_features(self):
222
tree = self.make_branch_and_tree('.', format='2a')
223
tree.update_feature_flags({"name": "necessity"})
224
found_format = workingtree.WorkingTreeFormatMetaDir.find_format(
226
self.assertIsInstance(found_format, workingtree.WorkingTreeFormat)
227
self.assertEquals(found_format.features.get("name"), "necessity")
228
self.assertRaises(errors.MissingFeature, found_format.check_support_status,
230
self.addCleanup(workingtree.WorkingTreeFormatMetaDir.unregister_feature,
232
workingtree.WorkingTreeFormatMetaDir.register_feature("name")
233
found_format.check_support_status(True)
236
class TestWorkingTreeIterEntriesByDir_wSubtrees(TestCaseWithTransport):
238
def make_simple_tree(self):
239
tree = self.make_branch_and_tree('tree', format='development-subtree')
240
self.build_tree(['tree/a/', 'tree/a/b/', 'tree/a/b/c'])
241
tree.set_root_id('root-id')
242
tree.add(['a', 'a/b', 'a/b/c'], ['a-id', 'b-id', 'c-id'])
243
tree.commit('initial')
246
def test_just_directory(self):
247
tree = self.make_simple_tree()
248
self.assertEqual([('directory', 'root-id'),
249
('directory', 'a-id'),
250
('directory', 'b-id'),
252
[(ie.kind, ie.file_id)
253
for path, ie in tree.iter_entries_by_dir()])
254
subtree = self.make_branch_and_tree('tree/a/b')
255
self.assertEqual([('tree-reference', 'b-id')],
256
[(ie.kind, ie.file_id)
257
for path, ie in tree.iter_entries_by_dir(['b-id'])])
259
def test_direct_subtree(self):
260
tree = self.make_simple_tree()
261
subtree = self.make_branch_and_tree('tree/a/b')
262
self.assertEqual([('directory', 'root-id'),
263
('directory', 'a-id'),
264
('tree-reference', 'b-id')],
265
[(ie.kind, ie.file_id)
266
for path, ie in tree.iter_entries_by_dir()])
268
def test_indirect_subtree(self):
269
tree = self.make_simple_tree()
270
subtree = self.make_branch_and_tree('tree/a')
271
self.assertEqual([('directory', 'root-id'),
272
('tree-reference', 'a-id')],
273
[(ie.kind, ie.file_id)
274
for path, ie in tree.iter_entries_by_dir()])
277
class TestWorkingTreeFormatRegistry(TestCase):
280
super(TestWorkingTreeFormatRegistry, self).setUp()
281
self.registry = workingtree.WorkingTreeFormatRegistry()
283
def test_register_unregister_format(self):
284
format = SampleTreeFormat()
285
self.registry.register(format)
286
self.assertEquals(format, self.registry.get("Sample tree format."))
287
self.registry.remove(format)
288
self.assertRaises(KeyError, self.registry.get, "Sample tree format.")
290
def test_get_all(self):
291
format = SampleTreeFormat()
292
self.assertEquals([], self.registry._get_all())
293
self.registry.register(format)
294
self.assertEquals([format], self.registry._get_all())
296
def test_register_extra(self):
297
format = SampleExtraTreeFormat()
298
self.assertEquals([], self.registry._get_all())
299
self.registry.register_extra(format)
300
self.assertEquals([format], self.registry._get_all())
302
def test_register_extra_lazy(self):
303
self.assertEquals([], self.registry._get_all())
304
self.registry.register_extra_lazy("bzrlib.tests.test_workingtree",
305
"SampleExtraTreeFormat")
306
formats = self.registry._get_all()
307
self.assertEquals(1, len(formats))
308
self.assertIsInstance(formats[0], SampleExtraTreeFormat)
311
class TestWorkingTreeFormat3(TestCaseWithTransport):
312
"""Tests specific to WorkingTreeFormat3."""
314
def test_disk_layout(self):
315
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
316
control.create_repository()
317
control.create_branch()
318
tree = workingtree_3.WorkingTreeFormat3().initialize(control)
320
# format 'Bazaar-NG Working Tree format 3'
321
# inventory = blank inventory
322
# pending-merges = ''
324
# no inventory.basis yet
325
t = control.get_workingtree_transport(None)
326
self.assertEqualDiff('Bazaar-NG Working Tree format 3',
327
t.get('format').read())
328
self.assertEqualDiff(t.get('inventory').read(),
329
'<inventory format="5">\n'
332
self.assertEqualDiff('### bzr hashcache v5\n',
333
t.get('stat-cache').read())
334
self.assertFalse(t.has('inventory.basis'))
335
# no last-revision file means 'None' or 'NULLREVISION'
336
self.assertFalse(t.has('last-revision'))
337
# TODO RBC 20060210 do a commit, check the inventory.basis is created
338
# correctly and last-revision file becomes present.
340
def test_uses_lockdir(self):
341
"""WorkingTreeFormat3 uses its own LockDir:
343
- lock is a directory
344
- when the WorkingTree is locked, LockDir can see that
346
t = self.get_transport()
348
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
349
repo = dir.create_repository()
350
branch = dir.create_branch()
352
tree = workingtree_3.WorkingTreeFormat3().initialize(dir)
353
except errors.NotLocalUrl:
354
raise TestSkipped('Not a local URL')
355
self.assertIsDirectory('.bzr', t)
356
self.assertIsDirectory('.bzr/checkout', t)
357
self.assertIsDirectory('.bzr/checkout/lock', t)
358
our_lock = LockDir(t, '.bzr/checkout/lock')
359
self.assertEquals(our_lock.peek(), None)
109
360
tree.lock_write()
110
self.assertEqual(1, tree.branch.control_files._lock_count)
111
self.assertEqual('w', tree.branch.control_files._lock_mode)
361
self.assertTrue(our_lock.peek())
113
self.assertEqual(None, tree.branch.control_files._lock_count)
115
def get_pullable_branches(self):
116
self.build_tree(['from/', 'from/file', 'to/'])
117
br_a = Branch.initialize('from')
118
tree = br_a.working_tree()
120
tree.commit('foo', rev_id='A')
121
br_b = Branch.initialize('to')
125
br_a, br_b = self.get_pullable_branches()
126
br_b.working_tree().pull(br_a)
127
self.failUnless(br_b.repository.has_revision('A'))
128
self.assertEqual(['A'], br_b.revision_history())
130
def test_pull_overwrites(self):
131
br_a, br_b = self.get_pullable_branches()
132
br_b.working_tree().commit('foo', rev_id='B')
133
self.assertEqual(['B'], br_b.revision_history())
134
br_b.working_tree().pull(br_a, overwrite=True)
135
self.failUnless(br_b.repository.has_revision('A'))
136
self.failUnless(br_b.repository.has_revision('B'))
137
self.assertEqual(['A'], br_b.revision_history())
139
def test_revert(self):
140
"""Test selected-file revert"""
141
b = Branch.initialize(u'.')
143
self.build_tree(['hello.txt'])
144
file('hello.txt', 'w').write('initial hello')
146
self.assertRaises(NotVersionedError,
147
b.working_tree().revert, ['hello.txt'])
148
tree = WorkingTree(b.base, b)
149
tree.add(['hello.txt'])
150
tree.commit('create initial hello.txt')
152
self.check_file_contents('hello.txt', 'initial hello')
153
file('hello.txt', 'w').write('new hello')
154
self.check_file_contents('hello.txt', 'new hello')
156
# revert file modified since last revision
157
tree.revert(['hello.txt'])
158
self.check_file_contents('hello.txt', 'initial hello')
159
self.check_file_contents('hello.txt~', 'new hello')
161
# reverting again does not clobber the backup
162
tree.revert(['hello.txt'])
163
self.check_file_contents('hello.txt', 'initial hello')
164
self.check_file_contents('hello.txt~', 'new hello')
166
def test_unknowns(self):
167
b = Branch.initialize(u'.')
168
tree = WorkingTree(u'.', b)
169
self.build_tree(['hello.txt',
171
self.assertEquals(list(tree.unknowns()),
174
def test_hashcache(self):
175
from bzrlib.tests.test_hashcache import pause
176
b = Branch.initialize(u'.')
177
tree = WorkingTree(u'.', b)
178
self.build_tree(['hello.txt',
180
tree.add('hello.txt')
182
sha = tree.get_file_sha1(tree.path2id('hello.txt'))
183
self.assertEqual(1, tree._hashcache.miss_count)
184
tree2 = WorkingTree(u'.', b)
185
sha2 = tree2.get_file_sha1(tree2.path2id('hello.txt'))
186
self.assertEqual(0, tree2._hashcache.miss_count)
187
self.assertEqual(1, tree2._hashcache.hit_count)
363
self.assertEquals(our_lock.peek(), None)
365
def test_missing_pending_merges(self):
366
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
367
control.create_repository()
368
control.create_branch()
369
tree = workingtree_3.WorkingTreeFormat3().initialize(control)
370
tree._transport.delete("pending-merges")
371
self.assertEqual([], tree.get_parent_ids())
374
class InstrumentedTree(object):
375
"""A instrumented tree to check the needs_tree_write_lock decorator."""
380
def lock_tree_write(self):
381
self._locks.append('t')
383
@needs_tree_write_lock
384
def method_with_tree_write_lock(self, *args, **kwargs):
385
"""A lock_tree_write decorated method that returns its arguments."""
388
@needs_tree_write_lock
389
def method_that_raises(self):
390
"""This method causes an exception when called with parameters.
392
This allows the decorator code to be checked - it should still call
397
self._locks.append('u')
400
class TestInstrumentedTree(TestCase):
402
def test_needs_tree_write_lock(self):
403
"""@needs_tree_write_lock should be semantically transparent."""
404
tree = InstrumentedTree()
406
'method_with_tree_write_lock',
407
tree.method_with_tree_write_lock.__name__)
408
self.assertDocstring(
409
"A lock_tree_write decorated method that returns its arguments.",
410
tree.method_with_tree_write_lock)
413
result = tree.method_with_tree_write_lock(1,2,3, a='b')
414
self.assertEqual((args, kwargs), result)
415
self.assertEqual(['t', 'u'], tree._locks)
416
self.assertRaises(TypeError, tree.method_that_raises, 'foo')
417
self.assertEqual(['t', 'u', 't', 'u'], tree._locks)
420
class TestRevert(TestCaseWithTransport):
422
def test_revert_conflicts_recursive(self):
423
this_tree = self.make_branch_and_tree('this-tree')
424
self.build_tree_contents([('this-tree/foo/',),
425
('this-tree/foo/bar', 'bar')])
426
this_tree.add(['foo', 'foo/bar'])
427
this_tree.commit('created foo/bar')
428
other_tree = this_tree.bzrdir.sprout('other-tree').open_workingtree()
429
self.build_tree_contents([('other-tree/foo/bar', 'baz')])
430
other_tree.commit('changed bar')
431
self.build_tree_contents([('this-tree/foo/bar', 'qux')])
432
this_tree.commit('changed qux')
433
this_tree.merge_from_branch(other_tree.branch)
434
self.assertEqual(1, len(this_tree.conflicts()))
435
this_tree.revert(['foo'])
436
self.assertEqual(0, len(this_tree.conflicts()))
439
class TestAutoResolve(TestCaseWithTransport):
441
def test_auto_resolve(self):
442
base = self.make_branch_and_tree('base')
443
self.build_tree_contents([('base/hello', 'Hello')])
444
base.add('hello', 'hello_id')
446
other = base.bzrdir.sprout('other').open_workingtree()
447
self.build_tree_contents([('other/hello', 'hELLO')])
448
other.commit('Case switch')
449
this = base.bzrdir.sprout('this').open_workingtree()
450
self.assertPathExists('this/hello')
451
self.build_tree_contents([('this/hello', 'Hello World')])
452
this.commit('Add World')
453
this.merge_from_branch(other.branch)
454
self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
457
self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
459
self.build_tree_contents([('this/hello', '<<<<<<<')])
461
self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
463
self.build_tree_contents([('this/hello', '=======')])
465
self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
467
self.build_tree_contents([('this/hello', '\n>>>>>>>')])
468
remaining, resolved = this.auto_resolve()
469
self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
471
self.assertEqual([], resolved)
472
self.build_tree_contents([('this/hello', 'hELLO wORLD')])
473
remaining, resolved = this.auto_resolve()
474
self.assertEqual([], this.conflicts())
475
self.assertEqual([conflicts.TextConflict('hello', 'hello_id')],
477
self.assertPathDoesNotExist('this/hello.BASE')
479
def test_auto_resolve_dir(self):
480
tree = self.make_branch_and_tree('tree')
481
self.build_tree(['tree/hello/'])
482
tree.add('hello', 'hello-id')
483
file_conflict = conflicts.TextConflict('file', 'hello-id')
484
tree.set_conflicts(conflicts.ConflictList([file_conflict]))
488
class TestFindTrees(TestCaseWithTransport):
490
def test_find_trees(self):
491
self.make_branch_and_tree('foo')
492
self.make_branch_and_tree('foo/bar')
493
# Sticking a tree inside a control dir is heinous, so let's skip it
494
self.make_branch_and_tree('foo/.bzr/baz')
495
self.make_branch('qux')
496
trees = workingtree.WorkingTree.find_trees('.')
497
self.assertEqual(2, len(list(trees)))
500
class TestStoredUncommitted(TestCaseWithTransport):
502
def store_uncommitted(self):
503
tree = self.make_branch_and_tree('tree')
504
tree.commit('get root in there')
505
self.build_tree_contents([('tree/file', 'content')])
506
tree.add('file', 'file-id')
507
tree.store_uncommitted()
510
def test_store_uncommitted(self):
511
self.store_uncommitted()
512
self.assertPathDoesNotExist('tree/file')
514
def test_store_uncommitted_no_change(self):
515
tree = self.make_branch_and_tree('tree')
516
tree.commit('get root in there')
517
tree.store_uncommitted()
518
self.assertIs(None, tree.branch.get_unshelver(tree))
520
def test_restore_uncommitted(self):
521
with write_locked(self.store_uncommitted()) as tree:
522
tree.restore_uncommitted()
523
self.assertPathExists('tree/file')
524
self.assertIs(None, tree.branch.get_unshelver(tree))
526
def test_restore_uncommitted_none(self):
527
tree = self.make_branch_and_tree('tree')
528
tree.restore_uncommitted()