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.tests import TestCase, TestCaseWithTransport, TestSkipped
21
from bzrlib.selftest import TestCaseInTempDir
31
22
from bzrlib.trace import mutter
32
from bzrlib.transport import get_transport
33
from bzrlib.workingtree import (
23
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
41
class TestTreeDirectory(TestCaseWithTransport):
26
class TestTreeDirectory(TestCaseInTempDir):
43
28
def test_kind_character(self):
44
29
self.assertEqual(TreeDirectory().kind_character(), '/')
47
class TestTreeEntry(TestCaseWithTransport):
32
class TestTreeEntry(TestCaseInTempDir):
49
34
def test_kind_character(self):
50
35
self.assertEqual(TreeEntry().kind_character(), '???')
53
class TestTreeFile(TestCaseWithTransport):
38
class TestTreeFile(TestCaseInTempDir):
55
40
def test_kind_character(self):
56
41
self.assertEqual(TreeFile().kind_character(), '')
59
class TestTreeLink(TestCaseWithTransport):
44
class TestTreeLink(TestCaseInTempDir):
61
46
def test_kind_character(self):
62
47
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.assertEqualDiff('<inventory format="5">\n'
178
t.get('inventory').read())
179
self.assertEqualDiff('### bzr hashcache v5\n',
180
t.get('stat-cache').read())
181
self.assertFalse(t.has('inventory.basis'))
182
# no last-revision file means 'None' or 'NULLREVISION'
183
self.assertFalse(t.has('last-revision'))
184
# TODO RBC 20060210 do a commit, check the inventory.basis is created
185
# correctly and last-revision file becomes present.
187
def test_uses_lockdir(self):
188
"""WorkingTreeFormat3 uses its own LockDir:
190
- lock is a directory
191
- when the WorkingTree is locked, LockDir can see that
193
t = self.get_transport()
195
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
196
repo = dir.create_repository()
197
branch = dir.create_branch()
199
tree = workingtree.WorkingTreeFormat3().initialize(dir)
200
except errors.NotLocalUrl:
201
raise TestSkipped('Not a local URL')
202
self.assertIsDirectory('.bzr', t)
203
self.assertIsDirectory('.bzr/checkout', t)
204
self.assertIsDirectory('.bzr/checkout/lock', t)
205
our_lock = LockDir(t, '.bzr/checkout/lock')
206
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)
207
106
tree.lock_write()
208
self.assertTrue(our_lock.peek())
107
self.assertEqual(1, tree.branch._lock_count)
108
self.assertEqual('w', tree.branch._lock_mode)
210
self.assertEquals(our_lock.peek(), None)
212
def test_missing_pending_merges(self):
213
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
214
control.create_repository()
215
control.create_branch()
216
tree = workingtree.WorkingTreeFormat3().initialize(control)
217
tree._control_files._transport.delete("pending-merges")
218
self.assertEqual([], tree.get_parent_ids())
221
class TestFormat2WorkingTree(TestCaseWithTransport):
222
"""Tests that are specific to format 2 trees."""
224
def create_format2_tree(self, url):
225
return self.make_branch_and_tree(
226
url, format=bzrlib.bzrdir.BzrDirFormat6())
228
def test_conflicts(self):
229
# test backwards compatability
230
tree = self.create_format2_tree('.')
231
self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
233
file('lala.BASE', 'wb').write('labase')
234
expected = conflicts.ContentsConflict('lala')
235
self.assertEqual(list(tree.conflicts()), [expected])
236
file('lala', 'wb').write('la')
237
tree.add('lala', 'lala-id')
238
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
239
self.assertEqual(list(tree.conflicts()), [expected])
240
file('lala.THIS', 'wb').write('lathis')
241
file('lala.OTHER', 'wb').write('laother')
242
# When "text conflict"s happen, stem, THIS and OTHER are text
243
expected = conflicts.TextConflict('lala', file_id='lala-id')
244
self.assertEqual(list(tree.conflicts()), [expected])
245
os.unlink('lala.OTHER')
246
os.mkdir('lala.OTHER')
247
expected = conflicts.ContentsConflict('lala', file_id='lala-id')
248
self.assertEqual(list(tree.conflicts()), [expected])
251
class TestNonFormatSpecificCode(TestCaseWithTransport):
252
"""This class contains tests of workingtree that are not format specific."""
255
def test_gen_file_id(self):
256
gen_file_id = bzrlib.workingtree.gen_file_id
258
# We try to use the filename if possible
259
self.assertStartsWith(gen_file_id('bar'), 'bar-')
261
# but we squash capitalization, and remove non word characters
262
self.assertStartsWith(gen_file_id('Mwoo oof\t m'), 'mwoooofm-')
264
# We also remove leading '.' characters to prevent hidden file-ids
265
self.assertStartsWith(gen_file_id('..gam.py'), 'gam.py-')
266
self.assertStartsWith(gen_file_id('..Mwoo oof\t m'), 'mwoooofm-')
268
# we remove unicode characters, and still don't end up with a
270
self.assertStartsWith(gen_file_id(u'\xe5\xb5.txt'), 'txt-')
272
# Our current method of generating unique ids adds 33 characters
273
# plus an serial number (log10(N) characters)
274
# to the end of the filename. We now restrict the filename portion to
275
# be <= 20 characters, so the maximum length should now be approx < 60
277
# Test both case squashing and length restriction
278
fid = gen_file_id('A'*50 + '.txt')
279
self.assertStartsWith(fid, 'a'*20 + '-')
280
self.failUnless(len(fid) < 60)
282
# restricting length happens after the other actions, so
283
# we preserve as much as possible
284
fid = gen_file_id('\xe5\xb5..aBcd\tefGhijKLMnop\tqrstuvwxyz')
285
self.assertStartsWith(fid, 'abcdefghijklmnopqrst-')
286
self.failUnless(len(fid) < 60)
288
def test_next_id_suffix(self):
289
bzrlib.workingtree._gen_id_suffix = None
290
bzrlib.workingtree._next_id_suffix()
291
self.assertNotEqual(None, bzrlib.workingtree._gen_id_suffix)
292
bzrlib.workingtree._gen_id_suffix = "foo-"
293
bzrlib.workingtree._gen_id_serial = 1
294
self.assertEqual("foo-2", bzrlib.workingtree._next_id_suffix())
295
self.assertEqual("foo-3", bzrlib.workingtree._next_id_suffix())
296
self.assertEqual("foo-4", bzrlib.workingtree._next_id_suffix())
297
self.assertEqual("foo-5", bzrlib.workingtree._next_id_suffix())
298
self.assertEqual("foo-6", bzrlib.workingtree._next_id_suffix())
299
self.assertEqual("foo-7", bzrlib.workingtree._next_id_suffix())
300
self.assertEqual("foo-8", bzrlib.workingtree._next_id_suffix())
301
self.assertEqual("foo-9", bzrlib.workingtree._next_id_suffix())
302
self.assertEqual("foo-10", bzrlib.workingtree._next_id_suffix())
304
def test__translate_ignore_rule(self):
305
tree = self.make_branch_and_tree('.')
306
# translation should return the regex, the number of groups in it,
307
# and the original rule in a tuple.
308
# there are three sorts of ignore rules:
309
# root only - regex is the rule itself without the leading ./
312
tree._translate_ignore_rule("./rootdirrule"))
313
# full path - regex is the rule itself
315
"(path\\/to\\/file$)",
316
tree._translate_ignore_rule("path/to/file"))
317
# basename only rule - regex is a rule that ignores everything up
318
# to the last / in the filename
320
"((?:.*/)?(?!.*/)basenamerule$)",
321
tree._translate_ignore_rule("basenamerule"))
323
def test__combine_ignore_rules(self):
324
tree = self.make_branch_and_tree('.')
325
# the combined ignore regexs need the outer group indices
326
# placed in a dictionary with the rules that were combined.
327
# an empty set of rules
328
# this is returned as a list of combined regex,rule sets, because
329
# python has a limit of 100 combined regexes.
330
compiled_rules = tree._combine_ignore_rules([])
331
self.assertEqual([], compiled_rules)
332
# one of each type of rule.
333
compiled_rules = tree._combine_ignore_rules(
334
["rule1", "rule/two", "./three"])[0]
335
# what type *is* the compiled regex to do an isinstance of ?
336
self.assertEqual(3, compiled_rules[0].groups)
338
{0:"rule1",1:"rule/two",2:"./three"},
341
def test__combine_ignore_rules_grouping(self):
342
tree = self.make_branch_and_tree('.')
343
# when there are too many rules, the output is split into groups of 100
345
for index in range(198):
347
self.assertEqual(2, len(tree._combine_ignore_rules(rules)))
349
def test__get_ignore_rules_as_regex(self):
350
tree = self.make_branch_and_tree('.')
351
# Setup the default ignore list to be empty
352
ignores._set_user_ignores([])
354
# some plugins (shelf) modifies the DEFAULT_IGNORE list in memory
355
# which causes this test to fail so force the DEFAULT_IGNORE
357
orig_default = bzrlib.DEFAULT_IGNORE
358
# Also make sure the runtime ignore list is empty
359
orig_runtime = ignores._runtime_ignores
361
bzrlib.DEFAULT_IGNORE = []
362
ignores._runtime_ignores = set()
364
self.build_tree_contents([('.bzrignore', 'CVS\n.hg\n')])
365
reference_output = tree._combine_ignore_rules(
366
set(['CVS', '.hg']))[0]
367
regex_rules = tree._get_ignore_rules_as_regex()[0]
368
self.assertEqual(len(reference_output[1]), regex_rules[0].groups)
369
self.assertEqual(reference_output[1], regex_rules[1])
371
bzrlib.DEFAULT_IGNORE = orig_default
372
ignores._runtime_ignores = orig_runtime
375
class InstrumentedTree(object):
376
"""A instrumented tree to check the needs_tree_write_lock decorator."""
381
def lock_tree_write(self):
382
self._locks.append('t')
384
@needs_tree_write_lock
385
def method_with_tree_write_lock(self, *args, **kwargs):
386
"""A lock_tree_write decorated method that returns its arguments."""
389
@needs_tree_write_lock
390
def method_that_raises(self):
391
"""This method causes an exception when called with parameters.
393
This allows the decorator code to be checked - it should still call
398
self._locks.append('u')
401
class TestInstrumentedTree(TestCase):
403
def test_needs_tree_write_lock(self):
404
"""@needs_tree_write_lock should be semantically transparent."""
405
tree = InstrumentedTree()
407
'method_with_tree_write_lock',
408
tree.method_with_tree_write_lock.__name__)
410
"A lock_tree_write decorated method that returns its arguments.",
411
tree.method_with_tree_write_lock.__doc__)
414
result = tree.method_with_tree_write_lock(1,2,3, a='b')
415
self.assertEqual((args, kwargs), result)
416
self.assertEqual(['t', 'u'], tree._locks)
417
self.assertRaises(TypeError, tree.method_that_raises, 'foo')
418
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()),