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.osutils import pathjoin, getcwd, has_symlinks
24
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
27
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):
29
44
def test_kind_character(self):
30
45
self.assertEqual(TreeDirectory().kind_character(), '/')
33
class TestTreeEntry(TestCaseInTempDir):
48
class TestTreeEntry(TestCaseWithTransport):
35
50
def test_kind_character(self):
36
51
self.assertEqual(TreeEntry().kind_character(), '???')
39
class TestTreeFile(TestCaseInTempDir):
54
class TestTreeFile(TestCaseWithTransport):
41
56
def test_kind_character(self):
42
57
self.assertEqual(TreeFile().kind_character(), '')
45
class TestTreeLink(TestCaseInTempDir):
60
class TestTreeLink(TestCaseWithTransport):
47
62
def test_kind_character(self):
48
63
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()))
66
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._lock_count)
106
self.assertEqual('r', tree.branch._lock_mode)
108
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
"""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.assertEqualDiff(t.get('inventory').read(),
178
'<inventory format="5">\n'
181
self.assertEqualDiff('### bzr hashcache v5\n',
182
t.get('stat-cache').read())
183
self.assertFalse(t.has('inventory.basis'))
184
# no last-revision file means 'None' or 'NULLREVISION'
185
self.assertFalse(t.has('last-revision'))
186
# TODO RBC 20060210 do a commit, check the inventory.basis is created
187
# correctly and last-revision file becomes present.
189
def test_uses_lockdir(self):
190
"""WorkingTreeFormat3 uses its own LockDir:
192
- lock is a directory
193
- when the WorkingTree is locked, LockDir can see that
195
t = self.get_transport()
197
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
198
repo = dir.create_repository()
199
branch = dir.create_branch()
201
tree = workingtree.WorkingTreeFormat3().initialize(dir)
202
except errors.NotLocalUrl:
203
raise TestSkipped('Not a local URL')
204
self.assertIsDirectory('.bzr', t)
205
self.assertIsDirectory('.bzr/checkout', t)
206
self.assertIsDirectory('.bzr/checkout/lock', t)
207
our_lock = LockDir(t, '.bzr/checkout/lock')
208
self.assertEquals(our_lock.peek(), None)
109
209
tree.lock_write()
110
self.assertEqual(1, tree.branch._lock_count)
111
self.assertEqual('w', tree.branch._lock_mode)
210
self.assertTrue(our_lock.peek())
113
self.assertEqual(None, tree.branch._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.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.has_revision('A'))
136
self.failUnless(br_b.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)
212
self.assertEquals(our_lock.peek(), None)
214
def test_missing_pending_merges(self):
215
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
216
control.create_repository()
217
control.create_branch()
218
tree = workingtree.WorkingTreeFormat3().initialize(control)
219
tree._control_files._transport.delete("pending-merges")
220
self.assertEqual([], tree.get_parent_ids())
223
class TestFormat2WorkingTree(TestCaseWithTransport):
224
"""Tests that are specific to format 2 trees."""
226
def create_format2_tree(self, url):
227
return self.make_branch_and_tree(
228
url, format=bzrdir.BzrDirFormat6())
230
def test_conflicts(self):
231
# test backwards compatability
232
tree = self.create_format2_tree('.')
233
self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
235
file('lala.BASE', 'wb').write('labase')
236
expected = conflicts.ContentsConflict('lala')
237
self.assertEqual(list(tree.conflicts()), [expected])
238
file('lala', 'wb').write('la')
239
tree.add('lala', 'lala-id')
240
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
241
self.assertEqual(list(tree.conflicts()), [expected])
242
file('lala.THIS', 'wb').write('lathis')
243
file('lala.OTHER', 'wb').write('laother')
244
# When "text conflict"s happen, stem, THIS and OTHER are text
245
expected = conflicts.TextConflict('lala', file_id='lala-id')
246
self.assertEqual(list(tree.conflicts()), [expected])
247
os.unlink('lala.OTHER')
248
os.mkdir('lala.OTHER')
249
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
250
self.assertEqual(list(tree.conflicts()), [expected])
253
class TestNonFormatSpecificCode(TestCaseWithTransport):
254
"""This class contains tests of workingtree that are not format specific."""
256
def test_gen_file_id(self):
257
file_id = self.applyDeprecated(zero_thirteen, workingtree.gen_file_id,
259
self.assertStartsWith(file_id, 'filename-')
261
def test_gen_root_id(self):
262
file_id = self.applyDeprecated(zero_thirteen, workingtree.gen_root_id)
263
self.assertStartsWith(file_id, 'tree_root-')
266
class InstrumentedTree(object):
267
"""A instrumented tree to check the needs_tree_write_lock decorator."""
272
def lock_tree_write(self):
273
self._locks.append('t')
275
@needs_tree_write_lock
276
def method_with_tree_write_lock(self, *args, **kwargs):
277
"""A lock_tree_write decorated method that returns its arguments."""
280
@needs_tree_write_lock
281
def method_that_raises(self):
282
"""This method causes an exception when called with parameters.
284
This allows the decorator code to be checked - it should still call
289
self._locks.append('u')
292
class TestInstrumentedTree(TestCase):
294
def test_needs_tree_write_lock(self):
295
"""@needs_tree_write_lock should be semantically transparent."""
296
tree = InstrumentedTree()
298
'method_with_tree_write_lock',
299
tree.method_with_tree_write_lock.__name__)
301
"A lock_tree_write decorated method that returns its arguments.",
302
tree.method_with_tree_write_lock.__doc__)
305
result = tree.method_with_tree_write_lock(1,2,3, a='b')
306
self.assertEqual((args, kwargs), result)
307
self.assertEqual(['t', 'u'], tree._locks)
308
self.assertRaises(TypeError, tree.method_that_raises, 'foo')
309
self.assertEqual(['t', 'u', 't', 'u'], tree._locks)
312
class TestRevert(TestCaseWithTransport):
314
def test_revert_conflicts_recursive(self):
315
this_tree = self.make_branch_and_tree('this-tree')
316
self.build_tree_contents([('this-tree/foo/',),
317
('this-tree/foo/bar', 'bar')])
318
this_tree.add(['foo', 'foo/bar'])
319
this_tree.commit('created foo/bar')
320
other_tree = this_tree.bzrdir.sprout('other-tree').open_workingtree()
321
self.build_tree_contents([('other-tree/foo/bar', 'baz')])
322
other_tree.commit('changed bar')
323
self.build_tree_contents([('this-tree/foo/bar', 'qux')])
324
this_tree.commit('changed qux')
325
this_tree.merge_from_branch(other_tree.branch)
326
self.assertEqual(1, len(this_tree.conflicts()))
327
this_tree.revert(['foo'])
328
self.assertEqual(0, len(this_tree.conflicts()))
331
class TestAutoResolve(TestCaseWithTransport):
333
def test_auto_resolve(self):
334
base = self.make_branch_and_tree('base')
335
self.build_tree_contents([('base/hello', 'Hello')])
336
base.add('hello', 'hello_id')
338
other = base.bzrdir.sprout('other').open_workingtree()
339
self.build_tree_contents([('other/hello', 'hELLO')])
340
other.commit('Case switch')
341
this = base.bzrdir.sprout('this').open_workingtree()
342
self.failUnlessExists('this/hello')
343
self.build_tree_contents([('this/hello', 'Hello World')])
344
this.commit('Add World')
345
this.merge_from_branch(other.branch)
346
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
349
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
351
self.build_tree_contents([('this/hello', '<<<<<<<')])
353
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
355
self.build_tree_contents([('this/hello', '=======')])
357
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
359
self.build_tree_contents([('this/hello', '\n>>>>>>>')])
360
remaining, resolved = this.auto_resolve()
361
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
363
self.assertEqual([], resolved)
364
self.build_tree_contents([('this/hello', 'hELLO wORLD')])
365
remaining, resolved = this.auto_resolve()
366
self.assertEqual([], this.conflicts())
367
self.assertEqual([conflicts.TextConflict('hello', None, 'hello_id')],
369
self.failIfExists('this/hello.BASE')
371
def test_auto_resolve_dir(self):
372
tree = self.make_branch_and_tree('tree')
373
self.build_tree(['tree/hello/'])
374
tree.add('hello', 'hello-id')
375
file_conflict = conflicts.TextConflict('file', None, 'hello-id')
376
tree.set_conflicts(conflicts.ConflictList([file_conflict]))