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
21
from bzrlib import ignores
23
19
from bzrlib.branch import Branch
24
from bzrlib import bzrdir, conflicts, errors, workingtree
25
from bzrlib.bzrdir import BzrDir
26
20
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
21
from bzrlib.tests import TestCaseInTempDir
32
22
from bzrlib.trace import mutter
33
from bzrlib.transport import get_transport
34
from bzrlib.workingtree import (
23
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
42
class TestTreeDirectory(TestCaseWithTransport):
26
class TestTreeDirectory(TestCaseInTempDir):
44
28
def test_kind_character(self):
45
29
self.assertEqual(TreeDirectory().kind_character(), '/')
48
class TestTreeEntry(TestCaseWithTransport):
32
class TestTreeEntry(TestCaseInTempDir):
50
34
def test_kind_character(self):
51
35
self.assertEqual(TreeEntry().kind_character(), '???')
54
class TestTreeFile(TestCaseWithTransport):
38
class TestTreeFile(TestCaseInTempDir):
56
40
def test_kind_character(self):
57
41
self.assertEqual(TreeFile().kind_character(), '')
60
class TestTreeLink(TestCaseWithTransport):
44
class TestTreeLink(TestCaseInTempDir):
62
46
def test_kind_character(self):
63
47
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)
50
class TestWorkingTree(TestCaseInTempDir):
52
def test_listfiles(self):
53
branch = Branch.initialize('.')
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('.')
65
wt, relpath = WorkingTree.open_containing()
66
self.assertEqual('', relpath)
67
self.assertEqual(wt.basedir, branch.base)
68
wt, relpath = WorkingTree.open_containing('.')
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('.')
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('.')
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('.')
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('.')
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)
215
106
tree.lock_write()
216
self.assertTrue(our_lock.peek())
107
self.assertEqual(1, tree.branch._lock_count)
108
self.assertEqual('w', tree.branch._lock_mode)
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 TestFormat2WorkingTree(TestCaseWithTransport):
230
"""Tests that are specific to format 2 trees."""
232
def create_format2_tree(self, url):
233
return self.make_branch_and_tree(
234
url, format=bzrlib.bzrdir.BzrDirFormat6())
236
def test_conflicts(self):
237
# test backwards compatability
238
tree = self.create_format2_tree('.')
239
self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
241
file('lala.BASE', 'wb').write('labase')
242
expected = conflicts.ContentsConflict('lala')
243
self.assertEqual(list(tree.conflicts()), [expected])
244
file('lala', 'wb').write('la')
245
tree.add('lala', 'lala-id')
246
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
247
self.assertEqual(list(tree.conflicts()), [expected])
248
file('lala.THIS', 'wb').write('lathis')
249
file('lala.OTHER', 'wb').write('laother')
250
# When "text conflict"s happen, stem, THIS and OTHER are text
251
expected = conflicts.TextConflict('lala', file_id='lala-id')
252
self.assertEqual(list(tree.conflicts()), [expected])
253
os.unlink('lala.OTHER')
254
os.mkdir('lala.OTHER')
255
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
256
self.assertEqual(list(tree.conflicts()), [expected])
259
class TestNonFormatSpecificCode(TestCaseWithTransport):
260
"""This class contains tests of workingtree that are not format specific."""
262
def test_gen_file_id(self):
263
file_id = self.applyDeprecated(zero_thirteen, workingtree.gen_file_id,
265
self.assertStartsWith(file_id, 'filename-')
267
def test_gen_root_id(self):
268
file_id = self.applyDeprecated(zero_thirteen, workingtree.gen_root_id)
269
self.assertStartsWith(file_id, 'tree_root-')
272
class InstrumentedTree(object):
273
"""A instrumented tree to check the needs_tree_write_lock decorator."""
278
def lock_tree_write(self):
279
self._locks.append('t')
281
@needs_tree_write_lock
282
def method_with_tree_write_lock(self, *args, **kwargs):
283
"""A lock_tree_write decorated method that returns its arguments."""
286
@needs_tree_write_lock
287
def method_that_raises(self):
288
"""This method causes an exception when called with parameters.
290
This allows the decorator code to be checked - it should still call
295
self._locks.append('u')
298
class TestInstrumentedTree(TestCase):
300
def test_needs_tree_write_lock(self):
301
"""@needs_tree_write_lock should be semantically transparent."""
302
tree = InstrumentedTree()
304
'method_with_tree_write_lock',
305
tree.method_with_tree_write_lock.__name__)
307
"A lock_tree_write decorated method that returns its arguments.",
308
tree.method_with_tree_write_lock.__doc__)
311
result = tree.method_with_tree_write_lock(1,2,3, a='b')
312
self.assertEqual((args, kwargs), result)
313
self.assertEqual(['t', 'u'], tree._locks)
314
self.assertRaises(TypeError, tree.method_that_raises, 'foo')
315
self.assertEqual(['t', 'u', 't', 'u'], tree._locks)
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('.')
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('.')
165
tree = WorkingTree('.', b)
166
self.build_tree(['hello.txt',
168
self.assertEquals(list(tree.unknowns()),