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
22
19
from bzrlib.branch import Branch
23
import bzrlib.bzrdir as bzrdir
24
from bzrlib.bzrdir import BzrDir
25
from bzrlib.conflicts import *
26
import bzrlib.errors as errors
27
20
from bzrlib.errors import NotBranchError, NotVersionedError
28
from bzrlib.lockdir import LockDir
21
from bzrlib.tests import TestCaseInTempDir
22
from bzrlib.trace import mutter
29
23
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
30
from bzrlib.tests import TestCaseWithTransport, TestSkipped
31
from bzrlib.trace import mutter
32
from bzrlib.transport import get_transport
33
import bzrlib.workingtree as workingtree
34
24
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
37
class TestTreeDirectory(TestCaseWithTransport):
27
class TestTreeDirectory(TestCaseInTempDir):
39
29
def test_kind_character(self):
40
30
self.assertEqual(TreeDirectory().kind_character(), '/')
43
class TestTreeEntry(TestCaseWithTransport):
33
class TestTreeEntry(TestCaseInTempDir):
45
35
def test_kind_character(self):
46
36
self.assertEqual(TreeEntry().kind_character(), '???')
49
class TestTreeFile(TestCaseWithTransport):
39
class TestTreeFile(TestCaseInTempDir):
51
41
def test_kind_character(self):
52
42
self.assertEqual(TreeFile().kind_character(), '')
55
class TestTreeLink(TestCaseWithTransport):
45
class TestTreeLink(TestCaseInTempDir):
57
47
def test_kind_character(self):
58
48
self.assertEqual(TreeLink().kind_character(), '')
61
class TestDefaultFormat(TestCaseWithTransport):
63
def test_get_set_default_format(self):
64
old_format = workingtree.WorkingTreeFormat.get_default_format()
66
self.assertTrue(isinstance(old_format, workingtree.WorkingTreeFormat3))
67
workingtree.WorkingTreeFormat.set_default_format(SampleTreeFormat())
69
# the default branch format is used by the meta dir format
70
# which is not the default bzrdir format at this point
71
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
72
dir.create_repository()
74
result = dir.create_workingtree()
75
self.assertEqual(result, 'A tree')
77
workingtree.WorkingTreeFormat.set_default_format(old_format)
78
self.assertEqual(old_format, workingtree.WorkingTreeFormat.get_default_format())
81
class SampleTreeFormat(workingtree.WorkingTreeFormat):
84
this format is initializable, unsupported to aid in testing the
85
open and open_downlevel routines.
88
def get_format_string(self):
89
"""See WorkingTreeFormat.get_format_string()."""
90
return "Sample tree format."
92
def initialize(self, a_bzrdir, revision_id=None):
93
"""Sample branches cannot be created."""
94
t = a_bzrdir.get_workingtree_transport(self)
95
t.put('format', StringIO(self.get_format_string()))
98
def is_supported(self):
101
def open(self, transport, _found=False):
102
return "opened tree."
105
class TestWorkingTreeFormat(TestCaseWithTransport):
106
"""Tests for the WorkingTreeFormat facility."""
108
def test_find_format(self):
109
# is the right format object found for a working tree?
110
# create a branch with a few known format objects.
111
self.build_tree(["foo/", "bar/"])
112
def check_format(format, url):
113
dir = format._matchingbzrdir.initialize(url)
114
dir.create_repository()
116
format.initialize(dir)
117
t = get_transport(url)
118
found_format = workingtree.WorkingTreeFormat.find_format(dir)
119
self.failUnless(isinstance(found_format, format.__class__))
120
check_format(workingtree.WorkingTreeFormat3(), "bar")
122
def test_find_format_no_tree(self):
123
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
124
self.assertRaises(errors.NoWorkingTree,
125
workingtree.WorkingTreeFormat.find_format,
128
def test_find_format_unknown_format(self):
129
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
130
dir.create_repository()
132
SampleTreeFormat().initialize(dir)
133
self.assertRaises(errors.UnknownFormatError,
134
workingtree.WorkingTreeFormat.find_format,
137
def test_register_unregister_format(self):
138
format = SampleTreeFormat()
140
dir = bzrdir.BzrDirMetaFormat1().initialize('.')
141
dir.create_repository()
144
format.initialize(dir)
145
# register a format for it.
146
workingtree.WorkingTreeFormat.register_format(format)
147
# which branch.Open will refuse (not supported)
148
self.assertRaises(errors.UnsupportedFormatError, workingtree.WorkingTree.open, '.')
149
# but open_downlevel will work
150
self.assertEqual(format.open(dir), workingtree.WorkingTree.open_downlevel('.'))
151
# unregister the format
152
workingtree.WorkingTreeFormat.unregister_format(format)
155
class TestWorkingTreeFormat3(TestCaseWithTransport):
156
"""Tests specific to WorkingTreeFormat3."""
158
def test_disk_layout(self):
159
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
160
control.create_repository()
161
control.create_branch()
162
tree = workingtree.WorkingTreeFormat3().initialize(control)
164
# format 'Bazaar-NG Working Tree format 3'
165
# inventory = blank inventory
166
# pending-merges = ''
168
# no inventory.basis yet
169
t = control.get_workingtree_transport(None)
170
self.assertEqualDiff('Bazaar-NG Working Tree format 3',
171
t.get('format').read())
172
self.assertEqualDiff('<inventory format="5">\n'
174
t.get('inventory').read())
175
self.assertEqualDiff('### bzr hashcache v5\n',
176
t.get('stat-cache').read())
177
self.assertFalse(t.has('inventory.basis'))
178
# no last-revision file means 'None' or 'NULLREVISION'
179
self.assertFalse(t.has('last-revision'))
180
# TODO RBC 20060210 do a commit, check the inventory.basis is created
181
# correctly and last-revision file becomes present.
183
def test_uses_lockdir(self):
184
"""WorkingTreeFormat3 uses its own LockDir:
186
- lock is a directory
187
- when the WorkingTree is locked, LockDir can see that
189
t = self.get_transport()
191
dir = bzrdir.BzrDirMetaFormat1().initialize(url)
192
repo = dir.create_repository()
193
branch = dir.create_branch()
195
tree = workingtree.WorkingTreeFormat3().initialize(dir)
196
except errors.NotLocalUrl:
197
raise TestSkipped('Not a local URL')
198
self.assertIsDirectory('.bzr', t)
199
self.assertIsDirectory('.bzr/checkout', t)
200
self.assertIsDirectory('.bzr/checkout/lock', t)
201
our_lock = LockDir(t, '.bzr/checkout/lock')
202
self.assertEquals(our_lock.peek(), None)
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)
203
109
tree.lock_write()
204
self.assertTrue(our_lock.peek())
110
self.assertEqual(1, tree.branch._lock_count)
111
self.assertEqual('w', tree.branch._lock_mode)
206
self.assertEquals(our_lock.peek(), None)
209
class TestFormat2WorkingTree(TestCaseWithTransport):
210
"""Tests that are specific to format 2 trees."""
212
def create_format2_tree(self, url):
213
return self.make_branch_and_tree(
214
url, format=bzrlib.bzrdir.BzrDirFormat6())
216
def test_conflicts(self):
217
# test backwards compatability
218
tree = self.create_format2_tree('.')
219
self.assertRaises(errors.UnsupportedOperation, tree.set_conflicts,
221
file('lala.BASE', 'wb').write('labase')
222
expected = ContentsConflict('lala')
223
self.assertEqual(list(tree.conflicts()), [expected])
224
file('lala', 'wb').write('la')
225
tree.add('lala', 'lala-id')
226
expected = ContentsConflict('lala', file_id='lala-id')
227
self.assertEqual(list(tree.conflicts()), [expected])
228
file('lala.THIS', 'wb').write('lathis')
229
file('lala.OTHER', 'wb').write('laother')
230
# When "text conflict"s happen, stem, THIS and OTHER are text
231
expected = TextConflict('lala', file_id='lala-id')
232
self.assertEqual(list(tree.conflicts()), [expected])
233
os.unlink('lala.OTHER')
234
os.mkdir('lala.OTHER')
235
expected = ContentsConflict('lala', file_id='lala-id')
236
self.assertEqual(list(tree.conflicts()), [expected])
239
class TestNonFormatSpecificCode(TestCaseWithTransport):
240
"""This class contains tests of workingtree that are not format specific."""
243
def test_gen_file_id(self):
244
self.assertStartsWith(bzrlib.workingtree.gen_file_id('bar'), 'bar-')
245
self.assertStartsWith(bzrlib.workingtree.gen_file_id('Mwoo oof\t m'), 'Mwoooofm-')
246
self.assertStartsWith(bzrlib.workingtree.gen_file_id('..gam.py'), 'gam.py-')
247
self.assertStartsWith(bzrlib.workingtree.gen_file_id('..Mwoo oof\t m'), 'Mwoooofm-')
249
def test_next_id_suffix(self):
250
bzrlib.workingtree._gen_id_suffix = None
251
bzrlib.workingtree._next_id_suffix()
252
self.assertNotEqual(None, bzrlib.workingtree._gen_id_suffix)
253
bzrlib.workingtree._gen_id_suffix = "foo-"
254
bzrlib.workingtree._gen_id_serial = 1
255
self.assertEqual("foo-2", bzrlib.workingtree._next_id_suffix())
256
self.assertEqual("foo-3", bzrlib.workingtree._next_id_suffix())
257
self.assertEqual("foo-4", bzrlib.workingtree._next_id_suffix())
258
self.assertEqual("foo-5", bzrlib.workingtree._next_id_suffix())
259
self.assertEqual("foo-6", bzrlib.workingtree._next_id_suffix())
260
self.assertEqual("foo-7", bzrlib.workingtree._next_id_suffix())
261
self.assertEqual("foo-8", bzrlib.workingtree._next_id_suffix())
262
self.assertEqual("foo-9", bzrlib.workingtree._next_id_suffix())
263
self.assertEqual("foo-10", bzrlib.workingtree._next_id_suffix())
265
def test__translate_ignore_rule(self):
266
tree = self.make_branch_and_tree('.')
267
# translation should return the regex, the number of groups in it,
268
# and the original rule in a tuple.
269
# there are three sorts of ignore rules:
270
# root only - regex is the rule itself without the leading ./
273
tree._translate_ignore_rule("./rootdirrule"))
274
# full path - regex is the rule itself
276
"(path\\/to\\/file$)",
277
tree._translate_ignore_rule("path/to/file"))
278
# basename only rule - regex is a rule that ignores everything up
279
# to the last / in the filename
281
"((?:.*/)?(?!.*/)basenamerule$)",
282
tree._translate_ignore_rule("basenamerule"))
284
def test__combine_ignore_rules(self):
285
tree = self.make_branch_and_tree('.')
286
# the combined ignore regexs need the outer group indices
287
# placed in a dictionary with the rules that were combined.
288
# an empty set of rules
289
# this is returned as a list of combined regex,rule sets, because
290
# python has a limit of 100 combined regexes.
291
compiled_rules = tree._combine_ignore_rules([])
292
self.assertEqual([], compiled_rules)
293
# one of each type of rule.
294
compiled_rules = tree._combine_ignore_rules(
295
["rule1", "rule/two", "./three"])[0]
296
# what type *is* the compiled regex to do an isinstance of ?
297
self.assertEqual(3, compiled_rules[0].groups)
299
{0:"rule1",1:"rule/two",2:"./three"},
302
def test__combine_ignore_rules_grouping(self):
303
tree = self.make_branch_and_tree('.')
304
# when there are too many rules, the output is split into groups of 100
306
for index in range(198):
308
self.assertEqual(2, len(tree._combine_ignore_rules(rules)))
310
def test__get_ignore_rules_as_regex(self):
311
tree = self.make_branch_and_tree('.')
312
# test against the default rules.
313
reference_output = tree._combine_ignore_rules(bzrlib.DEFAULT_IGNORE)[0]
314
regex_rules = tree._get_ignore_rules_as_regex()[0]
315
self.assertEqual(len(reference_output[1]), regex_rules[0].groups)
316
self.assertEqual(reference_output[1], regex_rules[1])
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)