15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from cStringIO import StringIO
19
27
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.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
26
class TestTreeDirectory(TestCaseInTempDir):
28
from bzrlib.bzrdir import BzrDir
29
from bzrlib.lockdir import LockDir
30
from bzrlib.mutabletree import needs_tree_write_lock
31
from bzrlib.symbol_versioning import zero_thirteen
32
from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
33
from bzrlib.transport import get_transport
34
from bzrlib.workingtree import (
42
class TestTreeDirectory(TestCaseWithTransport):
28
44
def test_kind_character(self):
29
45
self.assertEqual(TreeDirectory().kind_character(), '/')
32
class TestTreeEntry(TestCaseInTempDir):
48
class TestTreeEntry(TestCaseWithTransport):
34
50
def test_kind_character(self):
35
51
self.assertEqual(TreeEntry().kind_character(), '???')
38
class TestTreeFile(TestCaseInTempDir):
54
class TestTreeFile(TestCaseWithTransport):
40
56
def test_kind_character(self):
41
57
self.assertEqual(TreeFile().kind_character(), '')
44
class TestTreeLink(TestCaseInTempDir):
60
class TestTreeLink(TestCaseWithTransport):
46
62
def test_kind_character(self):
47
63
self.assertEqual(TreeLink().kind_character(), '')
50
class TestWorkingTree(TestCaseInTempDir):
52
def test_listfiles(self):
53
branch = Branch.initialize(u'.')
55
print >> open('file', 'w'), "content"
56
os.symlink('target', 'symlink')
57
tree = branch.working_tree()
58
files = list(tree.list_files())
59
self.assertEqual(files[0], ('dir', '?', 'directory', None, TreeDirectory()))
60
self.assertEqual(files[1], ('file', '?', 'file', None, TreeFile()))
61
self.assertEqual(files[2], ('symlink', '?', 'symlink', None, TreeLink()))
63
def test_open_containing(self):
64
branch = Branch.initialize(u'.')
65
wt, relpath = WorkingTree.open_containing()
66
self.assertEqual('', relpath)
67
self.assertEqual(wt.basedir, branch.base)
68
wt, relpath = WorkingTree.open_containing(u'.')
69
self.assertEqual('', relpath)
70
self.assertEqual(wt.basedir, branch.base)
71
wt, relpath = WorkingTree.open_containing('./foo')
72
self.assertEqual('foo', relpath)
73
self.assertEqual(wt.basedir, branch.base)
74
# paths that are urls are just plain wrong for working trees.
75
self.assertRaises(NotBranchError,
76
WorkingTree.open_containing,
77
'file:///' + os.getcwdu())
79
def test_construct_with_branch(self):
80
branch = Branch.initialize(u'.')
81
tree = WorkingTree(branch.base, branch)
82
self.assertEqual(branch, tree.branch)
83
self.assertEqual(branch.base, tree.basedir)
85
def test_construct_without_branch(self):
86
branch = Branch.initialize(u'.')
87
tree = WorkingTree(branch.base)
88
self.assertEqual(branch.base, tree.branch.base)
89
self.assertEqual(branch.base, tree.basedir)
91
def test_basic_relpath(self):
92
# for comprehensive relpath tests, see whitebox.py.
93
branch = Branch.initialize(u'.')
94
tree = WorkingTree(branch.base)
95
self.assertEqual('child',
96
tree.relpath(os.path.join(os.getcwd(), 'child')))
98
def test_lock_locks_branch(self):
99
branch = Branch.initialize(u'.')
100
tree = WorkingTree(branch.base)
102
self.assertEqual(1, tree.branch._lock_count)
103
self.assertEqual('r', tree.branch._lock_mode)
105
self.assertEqual(None, tree.branch._lock_count)
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, from_branch=None,
98
accelerator_tree=None):
99
"""Sample branches cannot be created."""
100
t = a_bzrdir.get_workingtree_transport(self)
101
t.put_bytes('format', self.get_format_string())
104
def is_supported(self):
107
def open(self, transport, _found=False):
108
return "opened tree."
111
class TestWorkingTreeFormat(TestCaseWithTransport):
112
"""Tests for the WorkingTreeFormat facility."""
114
def test_find_format(self):
115
# is the right format object found for a working tree?
116
# create a branch with a few known format objects.
117
self.build_tree(["foo/", "bar/"])
118
def check_format(format, url):
119
dir = format._matchingbzrdir.initialize(url)
120
dir.create_repository()
122
format.initialize(dir)
123
t = get_transport(url)
124
found_format = workingtree.WorkingTreeFormat.find_format(dir)
125
self.failUnless(isinstance(found_format, format.__class__))
126
check_format(workingtree.WorkingTreeFormat3(), "bar")
128
def test_find_format_no_tree(self):
129
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
130
self.assertRaises(errors.NoWorkingTree,
131
workingtree.WorkingTreeFormat.find_format,
134
def test_find_format_unknown_format(self):
135
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
136
dir.create_repository()
138
SampleTreeFormat().initialize(dir)
139
self.assertRaises(errors.UnknownFormatError,
140
workingtree.WorkingTreeFormat.find_format,
143
def test_register_unregister_format(self):
144
format = SampleTreeFormat()
146
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
147
dir.create_repository()
150
format.initialize(dir)
151
# register a format for it.
152
workingtree.WorkingTreeFormat.register_format(format)
153
# which branch.Open will refuse (not supported)
154
self.assertRaises(errors.UnsupportedFormatError, workingtree.WorkingTree.open, '.')
155
# but open_downlevel will work
156
self.assertEqual(format.open(dir), workingtree.WorkingTree.open_downlevel('.'))
157
# unregister the format
158
workingtree.WorkingTreeFormat.unregister_format(format)
161
class TestWorkingTreeFormat3(TestCaseWithTransport):
162
"""Tests specific to WorkingTreeFormat3."""
164
def test_disk_layout(self):
165
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
166
control.create_repository()
167
control.create_branch()
168
tree = workingtree.WorkingTreeFormat3().initialize(control)
170
# format 'Bazaar-NG Working Tree format 3'
171
# inventory = blank inventory
172
# pending-merges = ''
174
# no inventory.basis yet
175
t = control.get_workingtree_transport(None)
176
self.assertEqualDiff('Bazaar-NG Working Tree format 3',
177
t.get('format').read())
178
self.assertEqualDiff(t.get('inventory').read(),
179
'<inventory format="5">\n'
182
self.assertEqualDiff('### bzr hashcache v5\n',
183
t.get('stat-cache').read())
184
self.assertFalse(t.has('inventory.basis'))
185
# no last-revision file means 'None' or 'NULLREVISION'
186
self.assertFalse(t.has('last-revision'))
187
# TODO RBC 20060210 do a commit, check the inventory.basis is created
188
# correctly and last-revision file becomes present.
190
def test_uses_lockdir(self):
191
"""WorkingTreeFormat3 uses its own LockDir:
193
- lock is a directory
194
- when the WorkingTree is locked, LockDir can see that
196
t = self.get_transport()
198
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
199
repo = dir.create_repository()
200
branch = dir.create_branch()
202
tree = workingtree.WorkingTreeFormat3().initialize(dir)
203
except errors.NotLocalUrl:
204
raise TestSkipped('Not a local URL')
205
self.assertIsDirectory('.bzr', t)
206
self.assertIsDirectory('.bzr/checkout', t)
207
self.assertIsDirectory('.bzr/checkout/lock', t)
208
our_lock = LockDir(t, '.bzr/checkout/lock')
209
self.assertEquals(our_lock.peek(), None)
106
210
tree.lock_write()
107
self.assertEqual(1, tree.branch._lock_count)
108
self.assertEqual('w', tree.branch._lock_mode)
211
self.assertTrue(our_lock.peek())
110
self.assertEqual(None, tree.branch._lock_count)
112
def get_pullable_branches(self):
113
self.build_tree(['from/', 'from/file', 'to/'])
114
br_a = Branch.initialize('from')
115
tree = br_a.working_tree()
117
tree.commit('foo', rev_id='A')
118
br_b = Branch.initialize('to')
122
br_a, br_b = self.get_pullable_branches()
123
br_b.working_tree().pull(br_a)
124
self.failUnless(br_b.has_revision('A'))
125
self.assertEqual(['A'], br_b.revision_history())
127
def test_pull_overwrites(self):
128
br_a, br_b = self.get_pullable_branches()
129
br_b.working_tree().commit('foo', rev_id='B')
130
self.assertEqual(['B'], br_b.revision_history())
131
br_b.working_tree().pull(br_a, overwrite=True)
132
self.failUnless(br_b.has_revision('A'))
133
self.failUnless(br_b.has_revision('B'))
134
self.assertEqual(['A'], br_b.revision_history())
136
def test_revert(self):
137
"""Test selected-file revert"""
138
b = Branch.initialize(u'.')
140
self.build_tree(['hello.txt'])
141
file('hello.txt', 'w').write('initial hello')
143
self.assertRaises(NotVersionedError,
144
b.working_tree().revert, ['hello.txt'])
145
tree = WorkingTree(b.base, b)
146
tree.add(['hello.txt'])
147
tree.commit('create initial hello.txt')
149
self.check_file_contents('hello.txt', 'initial hello')
150
file('hello.txt', 'w').write('new hello')
151
self.check_file_contents('hello.txt', 'new hello')
153
# revert file modified since last revision
154
tree.revert(['hello.txt'])
155
self.check_file_contents('hello.txt', 'initial hello')
156
self.check_file_contents('hello.txt~', 'new hello')
158
# reverting again does not clobber the backup
159
tree.revert(['hello.txt'])
160
self.check_file_contents('hello.txt', 'initial hello')
161
self.check_file_contents('hello.txt~', 'new hello')
163
def test_unknowns(self):
164
b = Branch.initialize(u'.')
165
tree = WorkingTree(u'.', b)
166
self.build_tree(['hello.txt',
168
self.assertEquals(list(tree.unknowns()),
213
self.assertEquals(our_lock.peek(), None)
215
def test_missing_pending_merges(self):
216
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
217
control.create_repository()
218
control.create_branch()
219
tree = workingtree.WorkingTreeFormat3().initialize(control)
220
tree._control_files._transport.delete("pending-merges")
221
self.assertEqual([], tree.get_parent_ids())
224
class TestFormat2WorkingTree(TestCaseWithTransport):
225
"""Tests that are specific to format 2 trees."""
227
def create_format2_tree(self, url):
228
return self.make_branch_and_tree(
229
url, format=bzrdir.BzrDirFormat6())
231
def test_conflicts(self):
232
# test backwards compatability
233
tree = self.create_format2_tree('.')
234
self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
236
file('lala.BASE', 'wb').write('labase')
237
expected = conflicts.ContentsConflict('lala')
238
self.assertEqual(list(tree.conflicts()), [expected])
239
file('lala', 'wb').write('la')
240
tree.add('lala', 'lala-id')
241
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
242
self.assertEqual(list(tree.conflicts()), [expected])
243
file('lala.THIS', 'wb').write('lathis')
244
file('lala.OTHER', 'wb').write('laother')
245
# When "text conflict"s happen, stem, THIS and OTHER are text
246
expected = conflicts.TextConflict('lala', file_id='lala-id')
247
self.assertEqual(list(tree.conflicts()), [expected])
248
os.unlink('lala.OTHER')
249
os.mkdir('lala.OTHER')
250
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
251
self.assertEqual(list(tree.conflicts()), [expected])
254
class TestNonFormatSpecificCode(TestCaseWithTransport):
255
"""This class contains tests of workingtree that are not format specific."""
257
def test_gen_file_id(self):
258
file_id = self.applyDeprecated(zero_thirteen, workingtree.gen_file_id,
260
self.assertStartsWith(file_id, 'filename-')
262
def test_gen_root_id(self):
263
file_id = self.applyDeprecated(zero_thirteen, workingtree.gen_root_id)
264
self.assertStartsWith(file_id, 'tree_root-')
267
class InstrumentedTree(object):
268
"""A instrumented tree to check the needs_tree_write_lock decorator."""
273
def lock_tree_write(self):
274
self._locks.append('t')
276
@needs_tree_write_lock
277
def method_with_tree_write_lock(self, *args, **kwargs):
278
"""A lock_tree_write decorated method that returns its arguments."""
281
@needs_tree_write_lock
282
def method_that_raises(self):
283
"""This method causes an exception when called with parameters.
285
This allows the decorator code to be checked - it should still call
290
self._locks.append('u')
293
class TestInstrumentedTree(TestCase):
295
def test_needs_tree_write_lock(self):
296
"""@needs_tree_write_lock should be semantically transparent."""
297
tree = InstrumentedTree()
299
'method_with_tree_write_lock',
300
tree.method_with_tree_write_lock.__name__)
302
"A lock_tree_write decorated method that returns its arguments.",
303
tree.method_with_tree_write_lock.__doc__)
306
result = tree.method_with_tree_write_lock(1,2,3, a='b')
307
self.assertEqual((args, kwargs), result)
308
self.assertEqual(['t', 'u'], tree._locks)
309
self.assertRaises(TypeError, tree.method_that_raises, 'foo')
310
self.assertEqual(['t', 'u', 't', 'u'], tree._locks)
313
class TestRevert(TestCaseWithTransport):
315
def test_revert_conflicts_recursive(self):
316
this_tree = self.make_branch_and_tree('this-tree')
317
self.build_tree_contents([('this-tree/foo/',),
318
('this-tree/foo/bar', 'bar')])
319
this_tree.add(['foo', 'foo/bar'])
320
this_tree.commit('created foo/bar')
321
other_tree = this_tree.bzrdir.sprout('other-tree').open_workingtree()
322
self.build_tree_contents([('other-tree/foo/bar', 'baz')])
323
other_tree.commit('changed bar')
324
self.build_tree_contents([('this-tree/foo/bar', 'qux')])
325
this_tree.commit('changed qux')
326
this_tree.merge_from_branch(other_tree.branch)
327
self.assertEqual(1, len(this_tree.conflicts()))
328
this_tree.revert(['foo'])
329
self.assertEqual(0, len(this_tree.conflicts()))
332
class TestAutoResolve(TestCaseWithTransport):
334
def test_auto_resolve(self):
335
base = self.make_branch_and_tree('base')
336
self.build_tree_contents([('base/hello', 'Hello')])
337
base.add('hello', 'hello_id')
339
other = base.bzrdir.sprout('other').open_workingtree()
340
self.build_tree_contents([('other/hello', 'hELLO')])
341
other.commit('Case switch')
342
this = base.bzrdir.sprout('this').open_workingtree()
343
self.failUnlessExists('this/hello')
344
self.build_tree_contents([('this/hello', 'Hello World')])
345
this.commit('Add World')
346
this.merge_from_branch(other.branch)
347
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
350
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
352
self.build_tree_contents([('this/hello', '<<<<<<<')])
354
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
356
self.build_tree_contents([('this/hello', '=======')])
358
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
360
self.build_tree_contents([('this/hello', '\n>>>>>>>')])
361
remaining, resolved = this.auto_resolve()
362
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
364
self.assertEqual([], resolved)
365
self.build_tree_contents([('this/hello', 'hELLO wORLD')])
366
remaining, resolved = this.auto_resolve()
367
self.assertEqual([], this.conflicts())
368
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
370
self.failIfExists('this/hello.BASE')
372
def test_auto_resolve_dir(self):
373
tree = self.make_branch_and_tree('tree')
374
self.build_tree(['tree/hello/'])
375
tree.add('hello', 'hello-id')
376
file_conflict = conflicts.TextConflict('file', None, 'hello-id')
377
tree.set_conflicts(conflicts.ConflictList([file_conflict]))
381
class TestFindTrees(TestCaseWithTransport):
383
def test_find_trees(self):
384
self.make_branch_and_tree('foo')
385
self.make_branch_and_tree('foo/bar')
386
# Sticking a tree inside a control dir is heinous, so let's skip it
387
self.make_branch_and_tree('foo/.bzr/baz')
388
self.make_branch('qux')
389
trees = workingtree.WorkingTree.find_trees('.')
390
self.assertEqual(2, len(list(trees)))