1
# (C) 2005,2006 Canonical Ltd
2
# Authors: Robert Collins <robert.collins@canonical.com>
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from cStringIO import StringIO
23
from bzrlib.branch import Branch
24
import bzrlib.bzrdir as bzrdir
25
from bzrlib.bzrdir import BzrDir
26
import bzrlib.errors as errors
27
from bzrlib.errors import NotBranchError, NotVersionedError
28
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
29
from bzrlib.tests import TestSkipped
30
from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree
31
from bzrlib.trace import mutter
32
import bzrlib.workingtree as workingtree
33
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
37
class TestWorkingTree(TestCaseWithWorkingTree):
39
def test_listfiles(self):
40
tree = self.make_branch_and_tree('.')
42
print >> open('file', 'w'), "content"
44
os.symlink('target', 'symlink')
45
files = list(tree.list_files())
46
self.assertEqual(files[0], ('dir', '?', 'directory', None, TreeDirectory()))
47
self.assertEqual(files[1], ('file', '?', 'file', None, TreeFile()))
49
self.assertEqual(files[2], ('symlink', '?', 'symlink', None, TreeLink()))
51
def test_open_containing(self):
52
branch = self.make_branch_and_tree('.').branch
53
wt, relpath = WorkingTree.open_containing()
54
self.assertEqual('', relpath)
55
self.assertEqual(wt.basedir + '/', branch.base)
56
wt, relpath = WorkingTree.open_containing(u'.')
57
self.assertEqual('', relpath)
58
self.assertEqual(wt.basedir + '/', branch.base)
59
wt, relpath = WorkingTree.open_containing('./foo')
60
self.assertEqual('foo', relpath)
61
self.assertEqual(wt.basedir + '/', branch.base)
62
wt, relpath = WorkingTree.open_containing('file://' + getcwd() + '/foo')
63
self.assertEqual('foo', relpath)
64
self.assertEqual(wt.basedir + '/', branch.base)
66
def test_basic_relpath(self):
67
# for comprehensive relpath tests, see whitebox.py.
68
tree = self.make_branch_and_tree('.')
69
self.assertEqual('child',
70
tree.relpath(pathjoin(getcwd(), 'child')))
72
def test_lock_locks_branch(self):
73
tree = self.make_branch_and_tree('.')
75
self.assertEqual('r', tree.branch.peek_lock_mode())
77
self.assertEqual(None, tree.branch.peek_lock_mode())
79
self.assertEqual('w', tree.branch.peek_lock_mode())
81
self.assertEqual(None, tree.branch.peek_lock_mode())
83
def get_pullable_trees(self):
84
self.build_tree(['from/', 'from/file', 'to/'])
85
tree = self.make_branch_and_tree('from')
87
tree.commit('foo', rev_id='A')
88
tree_b = self.make_branch_and_tree('to')
92
tree_a, tree_b = self.get_pullable_trees()
93
tree_b.pull(tree_a.branch)
94
self.failUnless(tree_b.branch.repository.has_revision('A'))
95
self.assertEqual('A', tree_b.last_revision())
97
def test_pull_overwrites(self):
98
tree_a, tree_b = self.get_pullable_trees()
99
tree_b.commit('foo', rev_id='B')
100
self.assertEqual(['B'], tree_b.branch.revision_history())
101
tree_b.pull(tree_a.branch, overwrite=True)
102
self.failUnless(tree_b.branch.repository.has_revision('A'))
103
self.failUnless(tree_b.branch.repository.has_revision('B'))
104
self.assertEqual('A', tree_b.last_revision())
106
def test_revert(self):
107
"""Test selected-file revert"""
108
tree = self.make_branch_and_tree('.')
110
self.build_tree(['hello.txt'])
111
file('hello.txt', 'w').write('initial hello')
113
self.assertRaises(NotVersionedError,
114
tree.revert, ['hello.txt'])
115
tree.add(['hello.txt'])
116
tree.commit('create initial hello.txt')
118
self.check_file_contents('hello.txt', 'initial hello')
119
file('hello.txt', 'w').write('new hello')
120
self.check_file_contents('hello.txt', 'new hello')
122
# revert file modified since last revision
123
tree.revert(['hello.txt'])
124
self.check_file_contents('hello.txt', 'initial hello')
125
self.check_file_contents('hello.txt~', 'new hello')
127
# reverting again does not clobber the backup
128
tree.revert(['hello.txt'])
129
self.check_file_contents('hello.txt', 'initial hello')
130
self.check_file_contents('hello.txt~', 'new hello')
132
def test_unknowns(self):
133
tree = self.make_branch_and_tree('.')
134
self.build_tree(['hello.txt',
136
self.assertEquals(list(tree.unknowns()),
139
def test_hashcache(self):
140
from bzrlib.tests.test_hashcache import pause
141
tree = self.make_branch_and_tree('.')
142
self.build_tree(['hello.txt',
144
tree.add('hello.txt')
146
sha = tree.get_file_sha1(tree.path2id('hello.txt'))
147
self.assertEqual(1, tree._hashcache.miss_count)
148
tree2 = WorkingTree.open('.')
149
sha2 = tree2.get_file_sha1(tree2.path2id('hello.txt'))
150
self.assertEqual(0, tree2._hashcache.miss_count)
151
self.assertEqual(1, tree2._hashcache.hit_count)
153
def test_initialize(self):
154
# initialize should create a working tree and branch in an existing dir
155
t = self.make_branch_and_tree('.')
157
self.assertEqual(t.branch.base, b.base)
158
t2 = WorkingTree.open('.')
159
self.assertEqual(t.basedir, t2.basedir)
160
self.assertEqual(b.base, t2.branch.base)
161
# TODO maybe we should check the branch format? not sure if its
164
def test_rename_dirs(self):
165
"""Test renaming directories and the files within them."""
166
wt = self.make_branch_and_tree('.')
168
self.build_tree(['dir/', 'dir/sub/', 'dir/sub/file'])
169
wt.add(['dir', 'dir/sub', 'dir/sub/file'])
171
wt.commit('create initial state')
173
revid = b.revision_history()[0]
174
self.log('first revision_id is {%s}' % revid)
176
inv = b.repository.get_revision_inventory(revid)
177
self.log('contents of inventory: %r' % inv.entries())
179
self.check_inventory_shape(inv,
180
['dir', 'dir/sub', 'dir/sub/file'])
182
wt.rename_one('dir', 'newdir')
184
self.check_inventory_shape(wt.read_working_inventory(),
185
['newdir', 'newdir/sub', 'newdir/sub/file'])
187
wt.rename_one('newdir/sub', 'newdir/newsub')
188
self.check_inventory_shape(wt.read_working_inventory(),
189
['newdir', 'newdir/newsub',
190
'newdir/newsub/file'])
192
def test_add_in_unversioned(self):
193
"""Try to add a file in an unversioned directory.
195
"bzr add" adds the parent as necessary, but simple working tree add
198
from bzrlib.errors import NotVersionedError
199
wt = self.make_branch_and_tree('.')
200
self.build_tree(['foo/',
202
self.assertRaises(NotVersionedError,
206
def test_add_missing(self):
207
# adding a msising file -> NoSuchFile
208
wt = self.make_branch_and_tree('.')
209
self.assertRaises(errors.NoSuchFile, wt.add, 'fpp')
211
def test_remove_verbose(self):
212
#FIXME the remove api should not print or otherwise depend on the
213
# text UI - RBC 20060124
214
wt = self.make_branch_and_tree('.')
215
self.build_tree(['hello'])
217
wt.commit(message='add hello')
220
self.assertEqual(None, self.apply_redirected(None, stdout, stderr,
224
self.assertEqual('? hello\n', stdout.getvalue())
225
self.assertEqual('', stderr.getvalue())
227
def test_clone_trivial(self):
228
wt = self.make_branch_and_tree('source')
229
cloned_dir = wt.bzrdir.clone('target')
230
cloned = cloned_dir.open_workingtree()
231
self.assertEqual(cloned.last_revision(), wt.last_revision())
233
def test_last_revision(self):
234
wt = self.make_branch_and_tree('source')
235
self.assertEqual(None, wt.last_revision())
236
wt.commit('A', allow_pointless=True, rev_id='A')
237
self.assertEqual('A', wt.last_revision())
239
def test_set_last_revision(self):
240
wt = self.make_branch_and_tree('source')
241
self.assertEqual(None, wt.last_revision())
242
# cannot set the last revision to one not in the branch history.
243
self.assertRaises(errors.NoSuchRevision, wt.set_last_revision, 'A')
244
wt.commit('A', allow_pointless=True, rev_id='A')
245
self.assertEqual('A', wt.last_revision())
246
# None is aways in the branch
247
wt.set_last_revision(None)
248
self.assertEqual(None, wt.last_revision())
249
# and now we can set it to 'A'
250
# because some formats mutate the branch to set it on the tree
251
# we need to alter the branch to let this pass.
252
wt.branch.set_revision_history(['A', 'B'])
253
wt.set_last_revision('A')
254
self.assertEqual('A', wt.last_revision())
256
def test_set_last_revision_different_to_branch(self):
257
# working tree formats from the meta-dir format and newer support
258
# setting the last revision on a tree independently of that on the
259
# branch. Its concievable that some future formats may want to
260
# couple them again (i.e. because its really a smart server and
261
# the working tree will always match the branch). So we test
262
# that formats where initialising a branch does not initialise a
263
# tree - and thus have separable entities - support skewing the
265
branch = self.make_branch('tree')
267
# if there is a working tree now, this is not supported.
268
branch.bzrdir.open_workingtree()
270
except errors.NoWorkingTree:
272
wt = branch.bzrdir.create_workingtree()
273
wt.commit('A', allow_pointless=True, rev_id='A')
274
wt.set_last_revision(None)
275
self.assertEqual(None, wt.last_revision())
276
self.assertEqual('A', wt.branch.last_revision())
277
# and now we can set it back to 'A'
278
wt.set_last_revision('A')
279
self.assertEqual('A', wt.last_revision())
280
self.assertEqual('A', wt.branch.last_revision())
282
def test_clone_and_commit_preserves_last_revision(self):
283
wt = self.make_branch_and_tree('source')
284
cloned_dir = wt.bzrdir.clone('target')
285
wt.commit('A', allow_pointless=True, rev_id='A')
286
self.assertNotEqual(cloned_dir.open_workingtree().last_revision(),
289
def test_clone_preserves_content(self):
290
wt = self.make_branch_and_tree('source')
291
self.build_tree(['added', 'deleted', 'notadded'], transport=wt.bzrdir.transport.clone('..'))
292
wt.add('deleted', 'deleted')
293
wt.commit('add deleted')
295
wt.add('added', 'added')
296
cloned_dir = wt.bzrdir.clone('target')
297
cloned = cloned_dir.open_workingtree()
298
cloned_transport = cloned.bzrdir.transport.clone('..')
299
self.assertFalse(cloned_transport.has('deleted'))
300
self.assertTrue(cloned_transport.has('added'))
301
self.assertFalse(cloned_transport.has('notadded'))
302
self.assertEqual('added', cloned.path2id('added'))
303
self.assertEqual(None, cloned.path2id('deleted'))
304
self.assertEqual(None, cloned.path2id('notadded'))
306
def test_basis_tree_returns_last_revision(self):
307
wt = self.make_branch_and_tree('.')
308
self.build_tree(['foo'])
309
wt.add('foo', 'foo-id')
310
wt.commit('A', rev_id='A')
311
wt.rename_one('foo', 'bar')
312
wt.commit('B', rev_id='B')
313
wt.set_last_revision('B')
314
tree = wt.basis_tree()
315
self.failUnless(tree.has_filename('bar'))
316
wt.set_last_revision('A')
317
tree = wt.basis_tree()
318
self.failUnless(tree.has_filename('foo'))
320
def test_clone_tree_revision(self):
321
# make a tree with a last-revision,
322
# and clone it with a different last-revision, this should switch
325
# also test that the content is merged
326
# and conflicts recorded.
327
# This should merge between the trees - local edits should be preserved
328
# but other changes occured.
329
# we test this by having one file that does
330
# not change between two revisions, and another that does -
331
# if the changed one is not changed, fail,
332
# if the one that did not change has lost a local change, fail.
334
raise TestSkipped('revision limiting is not implemented yet.')
336
def test_initialize_with_revision_id(self):
337
# a bzrdir can construct a working tree for itself @ a specific revision.
338
source = self.make_branch_and_tree('source')
339
source.commit('a', rev_id='a', allow_pointless=True)
340
source.commit('b', rev_id='b', allow_pointless=True)
341
self.build_tree(['new/'])
342
made_control = self.bzrdir_format.initialize('new')
343
source.branch.repository.clone(made_control)
344
source.branch.clone(made_control)
345
made_tree = self.workingtree_format.initialize(made_control, revision_id='a')
346
self.assertEqual('a', made_tree.last_revision())
348
def test_commit_sets_last_revision(self):
349
tree = self.make_branch_and_tree('tree')
350
tree.commit('foo', rev_id='foo', allow_pointless=True)
351
self.assertEqual('foo', tree.last_revision())
353
def test_update_sets_last_revision(self):
354
# working tree formats from the meta-dir format and newer support
355
# setting the last revision on a tree independently of that on the
356
# branch. Its concievable that some future formats may want to
357
# couple them again (i.e. because its really a smart server and
358
# the working tree will always match the branch). So we test
359
# that formats where initialising a branch does not initialise a
360
# tree - and thus have separable entities - support skewing the
362
main_branch = self.make_branch('tree')
364
# if there is a working tree now, this is not supported.
365
main_branch.bzrdir.open_workingtree()
367
except errors.NoWorkingTree:
369
wt = main_branch.bzrdir.create_workingtree()
370
# create an out of date working tree by making a checkout in this
372
self.build_tree(['checkout/', 'tree/file'])
373
checkout = bzrdir.BzrDirMetaFormat1().initialize('checkout')
374
bzrlib.branch.BranchReferenceFormat().initialize(checkout, main_branch)
375
old_tree = self.workingtree_format.initialize(checkout)
376
# now commit to 'tree'
378
wt.commit('A', rev_id='A')
379
# and update old_tree
380
self.assertEqual(0, old_tree.update())
381
self.failUnlessExists('checkout/file')
382
self.assertEqual('A', old_tree.last_revision())
384
def test_update_returns_conflict_count(self):
385
# working tree formats from the meta-dir format and newer support
386
# setting the last revision on a tree independently of that on the
387
# branch. Its concievable that some future formats may want to
388
# couple them again (i.e. because its really a smart server and
389
# the working tree will always match the branch). So we test
390
# that formats where initialising a branch does not initialise a
391
# tree - and thus have separable entities - support skewing the
393
main_branch = self.make_branch('tree')
395
# if there is a working tree now, this is not supported.
396
main_branch.bzrdir.open_workingtree()
398
except errors.NoWorkingTree:
400
wt = main_branch.bzrdir.create_workingtree()
401
# create an out of date working tree by making a checkout in this
403
self.build_tree(['checkout/', 'tree/file'])
404
checkout = bzrdir.BzrDirMetaFormat1().initialize('checkout')
405
bzrlib.branch.BranchReferenceFormat().initialize(checkout, main_branch)
406
old_tree = self.workingtree_format.initialize(checkout)
407
# now commit to 'tree'
409
wt.commit('A', rev_id='A')
410
# and add a file file to the checkout
411
self.build_tree(['checkout/file'])
413
# and update old_tree
414
self.assertEqual(1, old_tree.update())
415
self.assertEqual('A', old_tree.last_revision())