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
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.tests import TestCase, TestCaseWithTransport, TestSkipped
20
from bzrlib.selftest import TestCaseInTempDir
31
21
from bzrlib.trace import mutter
32
from bzrlib.transport import get_transport
33
from bzrlib.workingtree import (
22
from bzrlib.workingtree import TreeEntry, TreeDirectory, TreeFile, TreeLink
41
class TestTreeDirectory(TestCaseWithTransport):
24
class TestTreeDirectory(TestCaseInTempDir):
43
26
def test_kind_character(self):
44
27
self.assertEqual(TreeDirectory().kind_character(), '/')
47
class TestTreeEntry(TestCaseWithTransport):
30
class TestTreeEntry(TestCaseInTempDir):
49
32
def test_kind_character(self):
50
33
self.assertEqual(TreeEntry().kind_character(), '???')
53
class TestTreeFile(TestCaseWithTransport):
36
class TestTreeFile(TestCaseInTempDir):
55
38
def test_kind_character(self):
56
39
self.assertEqual(TreeFile().kind_character(), '')
59
class TestTreeLink(TestCaseWithTransport):
42
class TestTreeLink(TestCaseInTempDir):
61
44
def test_kind_character(self):
62
45
self.assertEqual(TreeLink().kind_character(), '')
65
class TestDefaultFormat(TestCaseWithTransport):
67
def test_get_set_default_format(self):
68
old_format = workingtree.WorkingTreeFormat.get_default_format()
70
self.assertTrue(isinstance(old_format, workingtree.WorkingTreeFormat3))
71
workingtree.WorkingTreeFormat.set_default_format(SampleTreeFormat())
73
# the default branch format is used by the meta dir format
74
# which is not the default bzrdir format at this point
75
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
76
dir.create_repository()
78
result = dir.create_workingtree()
79
self.assertEqual(result, 'A tree')
81
workingtree.WorkingTreeFormat.set_default_format(old_format)
82
self.assertEqual(old_format, workingtree.WorkingTreeFormat.get_default_format())
85
class SampleTreeFormat(workingtree.WorkingTreeFormat):
88
this format is initializable, unsupported to aid in testing the
89
open and open_downlevel routines.
92
def get_format_string(self):
93
"""See WorkingTreeFormat.get_format_string()."""
94
return "Sample tree format."
96
def initialize(self, a_bzrdir, revision_id=None):
97
"""Sample branches cannot be created."""
98
t = a_bzrdir.get_workingtree_transport(self)
99
t.put_bytes('format', self.get_format_string())
102
def is_supported(self):
105
def open(self, transport, _found=False):
106
return "opened tree."
109
class TestWorkingTreeFormat(TestCaseWithTransport):
110
"""Tests for the WorkingTreeFormat facility."""
112
def test_find_format(self):
113
# is the right format object found for a working tree?
114
# create a branch with a few known format objects.
115
self.build_tree(["foo/", "bar/"])
116
def check_format(format, url):
117
dir = format._matchingbzrdir.initialize(url)
118
dir.create_repository()
120
format.initialize(dir)
121
t = get_transport(url)
122
found_format = workingtree.WorkingTreeFormat.find_format(dir)
123
self.failUnless(isinstance(found_format, format.__class__))
124
check_format(workingtree.WorkingTreeFormat3(), "bar")
126
def test_find_format_no_tree(self):
127
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
128
self.assertRaises(errors.NoWorkingTree,
129
workingtree.WorkingTreeFormat.find_format,
132
def test_find_format_unknown_format(self):
133
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
134
dir.create_repository()
136
SampleTreeFormat().initialize(dir)
137
self.assertRaises(errors.UnknownFormatError,
138
workingtree.WorkingTreeFormat.find_format,
141
def test_register_unregister_format(self):
142
format = SampleTreeFormat()
144
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
145
dir.create_repository()
148
format.initialize(dir)
149
# register a format for it.
150
workingtree.WorkingTreeFormat.register_format(format)
151
# which branch.Open will refuse (not supported)
152
self.assertRaises(errors.UnsupportedFormatError, workingtree.WorkingTree.open, '.')
153
# but open_downlevel will work
154
self.assertEqual(format.open(dir), workingtree.WorkingTree.open_downlevel('.'))
155
# unregister the format
156
workingtree.WorkingTreeFormat.unregister_format(format)
159
class TestWorkingTreeFormat3(TestCaseWithTransport):
160
"""Tests specific to WorkingTreeFormat3."""
162
def test_disk_layout(self):
163
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
164
control.create_repository()
165
control.create_branch()
166
tree = workingtree.WorkingTreeFormat3().initialize(control)
168
# format 'Bazaar-NG Working Tree format 3'
169
# inventory = blank inventory
170
# pending-merges = ''
172
# no inventory.basis yet
173
t = control.get_workingtree_transport(None)
174
self.assertEqualDiff('Bazaar-NG Working Tree format 3',
175
t.get('format').read())
176
# self.assertContainsRe(t.get('inventory').read(),
177
# '<inventory file_id="[^"]*" format="5">\n'
180
# WorkingTreeFormat3 doesn't default to creating a unique root id,
181
# because it is incompatible with older bzr versions
182
self.assertContainsRe(t.get('inventory').read(),
183
'<inventory format="5">\n'
186
self.assertEqualDiff('### bzr hashcache v5\n',
187
t.get('stat-cache').read())
188
self.assertFalse(t.has('inventory.basis'))
189
# no last-revision file means 'None' or 'NULLREVISION'
190
self.assertFalse(t.has('last-revision'))
191
# TODO RBC 20060210 do a commit, check the inventory.basis is created
192
# correctly and last-revision file becomes present.
194
def test_uses_lockdir(self):
195
"""WorkingTreeFormat3 uses its own LockDir:
197
- lock is a directory
198
- when the WorkingTree is locked, LockDir can see that
200
t = self.get_transport()
202
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
203
repo = dir.create_repository()
204
branch = dir.create_branch()
206
tree = workingtree.WorkingTreeFormat3().initialize(dir)
207
except errors.NotLocalUrl:
208
raise TestSkipped('Not a local URL')
209
self.assertIsDirectory('.bzr', t)
210
self.assertIsDirectory('.bzr/checkout', t)
211
self.assertIsDirectory('.bzr/checkout/lock', t)
212
our_lock = LockDir(t, '.bzr/checkout/lock')
213
self.assertEquals(our_lock.peek(), None)
215
self.assertTrue(our_lock.peek())
217
self.assertEquals(our_lock.peek(), None)
219
def test_missing_pending_merges(self):
220
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
221
control.create_repository()
222
control.create_branch()
223
tree = workingtree.WorkingTreeFormat3().initialize(control)
224
tree._control_files._transport.delete("pending-merges")
225
self.assertEqual([], tree.get_parent_ids())
228
class TestFormat2WorkingTree(TestCaseWithTransport):
229
"""Tests that are specific to format 2 trees."""
231
def create_format2_tree(self, url):
232
return self.make_branch_and_tree(
233
url, format=bzrlib.bzrdir.BzrDirFormat6())
235
def test_conflicts(self):
236
# test backwards compatability
237
tree = self.create_format2_tree('.')
238
self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
240
file('lala.BASE', 'wb').write('labase')
241
expected = conflicts.ContentsConflict('lala')
242
self.assertEqual(list(tree.conflicts()), [expected])
243
file('lala', 'wb').write('la')
244
tree.add('lala', 'lala-id')
245
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
246
self.assertEqual(list(tree.conflicts()), [expected])
247
file('lala.THIS', 'wb').write('lathis')
248
file('lala.OTHER', 'wb').write('laother')
249
# When "text conflict"s happen, stem, THIS and OTHER are text
250
expected = conflicts.TextConflict('lala', file_id='lala-id')
251
self.assertEqual(list(tree.conflicts()), [expected])
252
os.unlink('lala.OTHER')
253
os.mkdir('lala.OTHER')
254
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
255
self.assertEqual(list(tree.conflicts()), [expected])
258
class TestNonFormatSpecificCode(TestCaseWithTransport):
259
"""This class contains tests of workingtree that are not format specific."""
262
def test_gen_file_id(self):
263
gen_file_id = bzrlib.workingtree.gen_file_id
265
# We try to use the filename if possible
266
self.assertStartsWith(gen_file_id('bar'), 'bar-')
268
# but we squash capitalization, and remove non word characters
269
self.assertStartsWith(gen_file_id('Mwoo oof\t m'), 'mwoooofm-')
271
# We also remove leading '.' characters to prevent hidden file-ids
272
self.assertStartsWith(gen_file_id('..gam.py'), 'gam.py-')
273
self.assertStartsWith(gen_file_id('..Mwoo oof\t m'), 'mwoooofm-')
275
# we remove unicode characters, and still don't end up with a
277
self.assertStartsWith(gen_file_id(u'\xe5\xb5.txt'), 'txt-')
279
# Our current method of generating unique ids adds 33 characters
280
# plus an serial number (log10(N) characters)
281
# to the end of the filename. We now restrict the filename portion to
282
# be <= 20 characters, so the maximum length should now be approx < 60
284
# Test both case squashing and length restriction
285
fid = gen_file_id('A'*50 + '.txt')
286
self.assertStartsWith(fid, 'a'*20 + '-')
287
self.failUnless(len(fid) < 60)
289
# restricting length happens after the other actions, so
290
# we preserve as much as possible
291
fid = gen_file_id('\xe5\xb5..aBcd\tefGhijKLMnop\tqrstuvwxyz')
292
self.assertStartsWith(fid, 'abcdefghijklmnopqrst-')
293
self.failUnless(len(fid) < 60)
295
def test_next_id_suffix(self):
296
bzrlib.workingtree._gen_id_suffix = None
297
bzrlib.workingtree._next_id_suffix()
298
self.assertNotEqual(None, bzrlib.workingtree._gen_id_suffix)
299
bzrlib.workingtree._gen_id_suffix = "foo-"
300
bzrlib.workingtree._gen_id_serial = 1
301
self.assertEqual("foo-2", bzrlib.workingtree._next_id_suffix())
302
self.assertEqual("foo-3", bzrlib.workingtree._next_id_suffix())
303
self.assertEqual("foo-4", bzrlib.workingtree._next_id_suffix())
304
self.assertEqual("foo-5", bzrlib.workingtree._next_id_suffix())
305
self.assertEqual("foo-6", bzrlib.workingtree._next_id_suffix())
306
self.assertEqual("foo-7", bzrlib.workingtree._next_id_suffix())
307
self.assertEqual("foo-8", bzrlib.workingtree._next_id_suffix())
308
self.assertEqual("foo-9", bzrlib.workingtree._next_id_suffix())
309
self.assertEqual("foo-10", bzrlib.workingtree._next_id_suffix())
311
def test__translate_ignore_rule(self):
312
tree = self.make_branch_and_tree('.')
313
# translation should return the regex, the number of groups in it,
314
# and the original rule in a tuple.
315
# there are three sorts of ignore rules:
316
# root only - regex is the rule itself without the leading ./
319
tree._translate_ignore_rule("./rootdirrule"))
320
# full path - regex is the rule itself
322
"(path\\/to\\/file$)",
323
tree._translate_ignore_rule("path/to/file"))
324
# basename only rule - regex is a rule that ignores everything up
325
# to the last / in the filename
327
"((?:.*/)?(?!.*/)basenamerule$)",
328
tree._translate_ignore_rule("basenamerule"))
330
def test__combine_ignore_rules(self):
331
tree = self.make_branch_and_tree('.')
332
# the combined ignore regexs need the outer group indices
333
# placed in a dictionary with the rules that were combined.
334
# an empty set of rules
335
# this is returned as a list of combined regex,rule sets, because
336
# python has a limit of 100 combined regexes.
337
compiled_rules = tree._combine_ignore_rules([])
338
self.assertEqual([], compiled_rules)
339
# one of each type of rule.
340
compiled_rules = tree._combine_ignore_rules(
341
["rule1", "rule/two", "./three"])[0]
342
# what type *is* the compiled regex to do an isinstance of ?
343
self.assertEqual(3, compiled_rules[0].groups)
345
{0:"rule1",1:"rule/two",2:"./three"},
348
def test__combine_ignore_rules_grouping(self):
349
tree = self.make_branch_and_tree('.')
350
# when there are too many rules, the output is split into groups of 100
352
for index in range(198):
354
self.assertEqual(2, len(tree._combine_ignore_rules(rules)))
356
def test__get_ignore_rules_as_regex(self):
357
tree = self.make_branch_and_tree('.')
358
# Setup the default ignore list to be empty
359
ignores._set_user_ignores([])
361
# some plugins (shelf) modifies the DEFAULT_IGNORE list in memory
362
# which causes this test to fail so force the DEFAULT_IGNORE
364
orig_default = bzrlib.DEFAULT_IGNORE
365
# Also make sure the runtime ignore list is empty
366
orig_runtime = ignores._runtime_ignores
368
bzrlib.DEFAULT_IGNORE = []
369
ignores._runtime_ignores = set()
371
self.build_tree_contents([('.bzrignore', 'CVS\n.hg\n')])
372
reference_output = tree._combine_ignore_rules(
373
set(['CVS', '.hg']))[0]
374
regex_rules = tree._get_ignore_rules_as_regex()[0]
375
self.assertEqual(len(reference_output[1]), regex_rules[0].groups)
376
self.assertEqual(reference_output[1], regex_rules[1])
378
bzrlib.DEFAULT_IGNORE = orig_default
379
ignores._runtime_ignores = orig_runtime
382
class InstrumentedTree(object):
383
"""A instrumented tree to check the needs_tree_write_lock decorator."""
388
def lock_tree_write(self):
389
self._locks.append('t')
391
@needs_tree_write_lock
392
def method_with_tree_write_lock(self, *args, **kwargs):
393
"""A lock_tree_write decorated method that returns its arguments."""
396
@needs_tree_write_lock
397
def method_that_raises(self):
398
"""This method causes an exception when called with parameters.
400
This allows the decorator code to be checked - it should still call
405
self._locks.append('u')
408
class TestInstrumentedTree(TestCase):
410
def test_needs_tree_write_lock(self):
411
"""@needs_tree_write_lock should be semantically transparent."""
412
tree = InstrumentedTree()
414
'method_with_tree_write_lock',
415
tree.method_with_tree_write_lock.__name__)
417
"A lock_tree_write decorated method that returns its arguments.",
418
tree.method_with_tree_write_lock.__doc__)
421
result = tree.method_with_tree_write_lock(1,2,3, a='b')
422
self.assertEqual((args, kwargs), result)
423
self.assertEqual(['t', 'u'], tree._locks)
424
self.assertRaises(TypeError, tree.method_that_raises, 'foo')
425
self.assertEqual(['t', 'u', 't', 'u'], tree._locks)
48
class TestWorkingTree(TestCaseInTempDir):
50
def test_listfiles(self):
51
branch = Branch.initialize('.')
53
print >> open('file', 'w'), "content"
54
os.symlink('target', 'symlink')
55
tree = branch.working_tree()
56
files = list(tree.list_files())
57
self.assertEqual(files[0], ('dir', '?', 'directory', None, TreeDirectory()))
58
self.assertEqual(files[1], ('file', '?', 'file', None, TreeFile()))
59
self.assertEqual(files[2], ('symlink', '?', 'symlink', None, TreeLink()))