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 test_revert(self):
84
"""Test selected-file revert"""
85
tree = self.make_branch_and_tree('.')
87
self.build_tree(['hello.txt'])
88
file('hello.txt', 'w').write('initial hello')
90
self.assertRaises(NotVersionedError,
91
tree.revert, ['hello.txt'])
92
tree.add(['hello.txt'])
93
tree.commit('create initial hello.txt')
95
self.check_file_contents('hello.txt', 'initial hello')
96
file('hello.txt', 'w').write('new hello')
97
self.check_file_contents('hello.txt', 'new hello')
99
# revert file modified since last revision
100
tree.revert(['hello.txt'])
101
self.check_file_contents('hello.txt', 'initial hello')
102
self.check_file_contents('hello.txt~', 'new hello')
104
# reverting again does not clobber the backup
105
tree.revert(['hello.txt'])
106
self.check_file_contents('hello.txt', 'initial hello')
107
self.check_file_contents('hello.txt~', 'new hello')
109
def test_unknowns(self):
110
tree = self.make_branch_and_tree('.')
111
self.build_tree(['hello.txt',
113
self.assertEquals(list(tree.unknowns()),
116
def test_hashcache(self):
117
from bzrlib.tests.test_hashcache import pause
118
tree = self.make_branch_and_tree('.')
119
self.build_tree(['hello.txt',
121
tree.add('hello.txt')
123
sha = tree.get_file_sha1(tree.path2id('hello.txt'))
124
self.assertEqual(1, tree._hashcache.miss_count)
125
tree2 = WorkingTree.open('.')
126
sha2 = tree2.get_file_sha1(tree2.path2id('hello.txt'))
127
self.assertEqual(0, tree2._hashcache.miss_count)
128
self.assertEqual(1, tree2._hashcache.hit_count)
130
def test_initialize(self):
131
# initialize should create a working tree and branch in an existing dir
132
t = self.make_branch_and_tree('.')
134
self.assertEqual(t.branch.base, b.base)
135
t2 = WorkingTree.open('.')
136
self.assertEqual(t.basedir, t2.basedir)
137
self.assertEqual(b.base, t2.branch.base)
138
# TODO maybe we should check the branch format? not sure if its
141
def test_rename_dirs(self):
142
"""Test renaming directories and the files within them."""
143
wt = self.make_branch_and_tree('.')
145
self.build_tree(['dir/', 'dir/sub/', 'dir/sub/file'])
146
wt.add(['dir', 'dir/sub', 'dir/sub/file'])
148
wt.commit('create initial state')
150
revid = b.revision_history()[0]
151
self.log('first revision_id is {%s}' % revid)
153
inv = b.repository.get_revision_inventory(revid)
154
self.log('contents of inventory: %r' % inv.entries())
156
self.check_inventory_shape(inv,
157
['dir', 'dir/sub', 'dir/sub/file'])
159
wt.rename_one('dir', 'newdir')
161
self.check_inventory_shape(wt.read_working_inventory(),
162
['newdir', 'newdir/sub', 'newdir/sub/file'])
164
wt.rename_one('newdir/sub', 'newdir/newsub')
165
self.check_inventory_shape(wt.read_working_inventory(),
166
['newdir', 'newdir/newsub',
167
'newdir/newsub/file'])
169
def test_add_in_unversioned(self):
170
"""Try to add a file in an unversioned directory.
172
"bzr add" adds the parent as necessary, but simple working tree add
175
from bzrlib.errors import NotVersionedError
176
wt = self.make_branch_and_tree('.')
177
self.build_tree(['foo/',
179
self.assertRaises(NotVersionedError,
183
def test_add_missing(self):
184
# adding a msising file -> NoSuchFile
185
wt = self.make_branch_and_tree('.')
186
self.assertRaises(errors.NoSuchFile, wt.add, 'fpp')
188
def test_remove_verbose(self):
189
#FIXME the remove api should not print or otherwise depend on the
190
# text UI - RBC 20060124
191
wt = self.make_branch_and_tree('.')
192
self.build_tree(['hello'])
194
wt.commit(message='add hello')
197
self.assertEqual(None, self.apply_redirected(None, stdout, stderr,
201
self.assertEqual('? hello\n', stdout.getvalue())
202
self.assertEqual('', stderr.getvalue())
204
def test_clone_trivial(self):
205
wt = self.make_branch_and_tree('source')
206
cloned_dir = wt.bzrdir.clone('target')
207
cloned = cloned_dir.open_workingtree()
208
self.assertEqual(cloned.last_revision(), wt.last_revision())
210
def test_last_revision(self):
211
wt = self.make_branch_and_tree('source')
212
self.assertEqual(None, wt.last_revision())
213
wt.commit('A', allow_pointless=True, rev_id='A')
214
self.assertEqual('A', wt.last_revision())
216
def test_set_last_revision(self):
217
wt = self.make_branch_and_tree('source')
218
self.assertEqual(None, wt.last_revision())
219
# cannot set the last revision to one not in the branch history.
220
self.assertRaises(errors.NoSuchRevision, wt.set_last_revision, 'A')
221
wt.commit('A', allow_pointless=True, rev_id='A')
222
self.assertEqual('A', wt.last_revision())
223
# None is aways in the branch
224
wt.set_last_revision(None)
225
self.assertEqual(None, wt.last_revision())
226
# and now we can set it to 'A'
227
# because some formats mutate the branch to set it on the tree
228
# we need to alter the branch to let this pass.
229
wt.branch.set_revision_history(['A', 'B'])
230
wt.set_last_revision('A')
231
self.assertEqual('A', wt.last_revision())
233
def test_set_last_revision_different_to_branch(self):
234
# working tree formats from the meta-dir format and newer support
235
# setting the last revision on a tree independently of that on the
236
# branch. Its concievable that some future formats may want to
237
# couple them again (i.e. because its really a smart server and
238
# the working tree will always match the branch). So we test
239
# that formats where initialising a branch does not initialise a
240
# tree - and thus have separable entities - support skewing the
242
branch = self.make_branch('tree')
244
# if there is a working tree now, this is not supported.
245
branch.bzrdir.open_workingtree()
247
except errors.NoWorkingTree:
249
wt = branch.bzrdir.create_workingtree()
250
wt.commit('A', allow_pointless=True, rev_id='A')
251
wt.set_last_revision(None)
252
self.assertEqual(None, wt.last_revision())
253
self.assertEqual('A', wt.branch.last_revision())
254
# and now we can set it back to 'A'
255
wt.set_last_revision('A')
256
self.assertEqual('A', wt.last_revision())
257
self.assertEqual('A', wt.branch.last_revision())
259
def test_clone_and_commit_preserves_last_revision(self):
260
wt = self.make_branch_and_tree('source')
261
cloned_dir = wt.bzrdir.clone('target')
262
wt.commit('A', allow_pointless=True, rev_id='A')
263
self.assertNotEqual(cloned_dir.open_workingtree().last_revision(),
266
def test_clone_preserves_content(self):
267
wt = self.make_branch_and_tree('source')
268
self.build_tree(['added', 'deleted', 'notadded'], transport=wt.bzrdir.transport.clone('..'))
269
wt.add('deleted', 'deleted')
270
wt.commit('add deleted')
272
wt.add('added', 'added')
273
cloned_dir = wt.bzrdir.clone('target')
274
cloned = cloned_dir.open_workingtree()
275
cloned_transport = cloned.bzrdir.transport.clone('..')
276
self.assertFalse(cloned_transport.has('deleted'))
277
self.assertTrue(cloned_transport.has('added'))
278
self.assertFalse(cloned_transport.has('notadded'))
279
self.assertEqual('added', cloned.path2id('added'))
280
self.assertEqual(None, cloned.path2id('deleted'))
281
self.assertEqual(None, cloned.path2id('notadded'))
283
def test_basis_tree_returns_last_revision(self):
284
wt = self.make_branch_and_tree('.')
285
self.build_tree(['foo'])
286
wt.add('foo', 'foo-id')
287
wt.commit('A', rev_id='A')
288
wt.rename_one('foo', 'bar')
289
wt.commit('B', rev_id='B')
290
wt.set_last_revision('B')
291
tree = wt.basis_tree()
292
self.failUnless(tree.has_filename('bar'))
293
wt.set_last_revision('A')
294
tree = wt.basis_tree()
295
self.failUnless(tree.has_filename('foo'))
297
def test_clone_tree_revision(self):
298
# make a tree with a last-revision,
299
# and clone it with a different last-revision, this should switch
302
# also test that the content is merged
303
# and conflicts recorded.
304
# This should merge between the trees - local edits should be preserved
305
# but other changes occured.
306
# we test this by having one file that does
307
# not change between two revisions, and another that does -
308
# if the changed one is not changed, fail,
309
# if the one that did not change has lost a local change, fail.
311
raise TestSkipped('revision limiting is not implemented yet.')
313
def test_initialize_with_revision_id(self):
314
# a bzrdir can construct a working tree for itself @ a specific revision.
315
source = self.make_branch_and_tree('source')
316
source.commit('a', rev_id='a', allow_pointless=True)
317
source.commit('b', rev_id='b', allow_pointless=True)
318
self.build_tree(['new/'])
319
made_control = self.bzrdir_format.initialize('new')
320
source.branch.repository.clone(made_control)
321
source.branch.clone(made_control)
322
made_tree = self.workingtree_format.initialize(made_control, revision_id='a')
323
self.assertEqual('a', made_tree.last_revision())
325
def test_commit_sets_last_revision(self):
326
tree = self.make_branch_and_tree('tree')
327
tree.commit('foo', rev_id='foo', allow_pointless=True)
328
self.assertEqual('foo', tree.last_revision())
330
def test_commit_local_unbound(self):
331
# using the library api to do a local commit on unbound branches is
333
tree = self.make_branch_and_tree('tree')
334
self.assertRaises(errors.LocalRequiresBoundBranch,
339
def test_local_commit_ignores_master(self):
340
# a --local commit does not require access to the master branch
341
# at all, or even for it to exist.
342
# we test this by setting up a bound branch and then corrupting
344
master = self.make_branch('master')
345
tree = self.make_branch_and_tree('tree')
347
tree.branch.bind(master)
348
except errors.UpgradeRequired:
351
master.bzrdir.transport.put('branch-format', StringIO('garbage'))
353
# check its corrupted.
354
self.assertRaises(errors.UnknownFormatError,
357
tree.commit('foo', rev_id='foo', local=True)
359
def test_local_commit_does_not_push_to_master(self):
360
# a --local commit does not require access to the master branch
361
# at all, or even for it to exist.
362
# we test that even when its available it does not push to it.
363
master = self.make_branch('master')
364
tree = self.make_branch_and_tree('tree')
366
tree.branch.bind(master)
367
except errors.UpgradeRequired:
370
tree.commit('foo', rev_id='foo', local=True)
371
self.failIf(master.repository.has_revision('foo'))
372
self.assertEqual(None, master.last_revision())
374
def test_update_sets_last_revision(self):
375
# working tree formats from the meta-dir format and newer support
376
# setting the last revision on a tree independently of that on the
377
# branch. Its concievable that some future formats may want to
378
# couple them again (i.e. because its really a smart server and
379
# the working tree will always match the branch). So we test
380
# that formats where initialising a branch does not initialise a
381
# tree - and thus have separable entities - support skewing the
383
main_branch = self.make_branch('tree')
385
# if there is a working tree now, this is not supported.
386
main_branch.bzrdir.open_workingtree()
388
except errors.NoWorkingTree:
390
wt = main_branch.bzrdir.create_workingtree()
391
# create an out of date working tree by making a checkout in this
393
self.build_tree(['checkout/', 'tree/file'])
394
checkout = bzrdir.BzrDirMetaFormat1().initialize('checkout')
395
bzrlib.branch.BranchReferenceFormat().initialize(checkout, main_branch)
396
old_tree = self.workingtree_format.initialize(checkout)
397
# now commit to 'tree'
399
wt.commit('A', rev_id='A')
400
# and update old_tree
401
self.assertEqual(0, old_tree.update())
402
self.failUnlessExists('checkout/file')
403
self.assertEqual('A', old_tree.last_revision())
405
def test_update_returns_conflict_count(self):
406
# working tree formats from the meta-dir format and newer support
407
# setting the last revision on a tree independently of that on the
408
# branch. Its concievable that some future formats may want to
409
# couple them again (i.e. because its really a smart server and
410
# the working tree will always match the branch). So we test
411
# that formats where initialising a branch does not initialise a
412
# tree - and thus have separable entities - support skewing the
414
main_branch = self.make_branch('tree')
416
# if there is a working tree now, this is not supported.
417
main_branch.bzrdir.open_workingtree()
419
except errors.NoWorkingTree:
421
wt = main_branch.bzrdir.create_workingtree()
422
# create an out of date working tree by making a checkout in this
424
self.build_tree(['checkout/', 'tree/file'])
425
checkout = bzrdir.BzrDirMetaFormat1().initialize('checkout')
426
bzrlib.branch.BranchReferenceFormat().initialize(checkout, main_branch)
427
old_tree = self.workingtree_format.initialize(checkout)
428
# now commit to 'tree'
430
wt.commit('A', rev_id='A')
431
# and add a file file to the checkout
432
self.build_tree(['checkout/file'])
434
# and update old_tree
435
self.assertEqual(1, old_tree.update())
436
self.assertEqual('A', old_tree.last_revision())
438
def test_update_updates_bound_branch_no_local_commits(self):
439
# doing an update in a tree updates the branch its bound to too.
440
master_tree = self.make_branch_and_tree('master')
441
tree = self.make_branch_and_tree('tree')
443
tree.branch.bind(master_tree.branch)
444
except errors.UpgradeRequired:
445
# legacy branches cannot bind
447
master_tree.commit('foo', rev_id='foo', allow_pointless=True)
449
self.assertEqual('foo', tree.last_revision())
450
self.assertEqual('foo', tree.branch.last_revision())
452
def test_update_turns_local_commit_into_merge(self):
453
# doing an update with a few local commits and no master commits
454
# makes pending-merges.
455
# this is done so that 'bzr update; bzr revert' will always produce
456
# an exact copy of the 'logical branch' - the referenced branch for
457
# a checkout, and the master for a bound branch.
458
# its possible that we should instead have 'bzr update' when there
459
# is nothing new on the master leave the current commits intact and
460
# alter 'revert' to revert to the master always. But for now, its
462
master_tree = self.make_branch_and_tree('master')
463
tree = self.make_branch_and_tree('tree')
465
tree.branch.bind(master_tree.branch)
466
except errors.UpgradeRequired:
467
# legacy branches cannot bind
469
tree.commit('foo', rev_id='foo', allow_pointless=True, local=True)
470
tree.commit('bar', rev_id='bar', allow_pointless=True, local=True)
472
self.assertEqual(None, tree.last_revision())
473
self.assertEqual([], tree.branch.revision_history())
474
self.assertEqual(['bar'], tree.pending_merges())