1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
# Authors: Robert Collins <robert.collins@canonical.com>
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from cStringIO import StringIO
33
from bzrlib.errors import (NotBranchError, NotVersionedError,
34
UnsupportedOperation, PathsNotVersionedError)
35
from bzrlib.inventory import Inventory
36
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
37
from bzrlib.tests import TestSkipped, TestNotApplicable
38
from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree
39
from bzrlib.trace import mutter
40
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
41
WorkingTree, WorkingTree2)
42
from bzrlib.conflicts import ConflictList, TextConflict, ContentsConflict
45
class TestWorkingTree(TestCaseWithWorkingTree):
47
def test_list_files(self):
48
tree = self.make_branch_and_tree('.')
49
self.build_tree(['dir/', 'file'])
51
os.symlink('target', 'symlink')
53
files = list(tree.list_files())
55
self.assertEqual(files[0], ('dir', '?', 'directory', None, TreeDirectory()))
56
self.assertEqual(files[1], ('file', '?', 'file', None, TreeFile()))
58
self.assertEqual(files[2], ('symlink', '?', 'symlink', None, TreeLink()))
60
def test_list_files_sorted(self):
61
tree = self.make_branch_and_tree('.')
62
self.build_tree(['dir/', 'file', 'dir/file', 'dir/b',
63
'dir/subdir/', 'a', 'dir/subfile',
64
'zz_dir/', 'zz_dir/subfile'])
66
files = [(path, kind) for (path, v, kind, file_id, entry)
73
('zz_dir', 'directory'),
76
tree.add(['dir', 'zz_dir'])
78
files = [(path, kind) for (path, v, kind, file_id, entry)
86
('dir/subdir', 'directory'),
87
('dir/subfile', 'file'),
89
('zz_dir', 'directory'),
90
('zz_dir/subfile', 'file'),
93
def test_list_files_kind_change(self):
94
tree = self.make_branch_and_tree('tree')
95
self.build_tree(['tree/filename'])
96
tree.add('filename', 'file-id')
97
os.unlink('tree/filename')
98
self.build_tree(['tree/filename/'])
100
self.addCleanup(tree.unlock)
101
result = list(tree.list_files())
102
self.assertEqual(1, len(result))
103
self.assertEqual(('filename', 'V', 'directory', 'file-id'),
106
def test_open_containing(self):
107
branch = self.make_branch_and_tree('.').branch
108
local_base = urlutils.local_path_from_url(branch.base)
111
wt, relpath = WorkingTree.open_containing()
112
self.assertEqual('', relpath)
113
self.assertEqual(wt.basedir + '/', local_base)
116
wt, relpath = WorkingTree.open_containing(u'.')
117
self.assertEqual('', relpath)
118
self.assertEqual(wt.basedir + '/', local_base)
120
# './foo' finds '.' and a relpath of 'foo'
121
wt, relpath = WorkingTree.open_containing('./foo')
122
self.assertEqual('foo', relpath)
123
self.assertEqual(wt.basedir + '/', local_base)
125
# abspath(foo) finds '.' and relpath of 'foo'
126
wt, relpath = WorkingTree.open_containing('./foo')
127
wt, relpath = WorkingTree.open_containing(getcwd() + '/foo')
128
self.assertEqual('foo', relpath)
129
self.assertEqual(wt.basedir + '/', local_base)
131
# can even be a url: finds '.' and relpath of 'foo'
132
wt, relpath = WorkingTree.open_containing('./foo')
133
wt, relpath = WorkingTree.open_containing(
134
urlutils.local_path_to_url(getcwd() + '/foo'))
135
self.assertEqual('foo', relpath)
136
self.assertEqual(wt.basedir + '/', local_base)
138
def test_basic_relpath(self):
139
# for comprehensive relpath tests, see whitebox.py.
140
tree = self.make_branch_and_tree('.')
141
self.assertEqual('child',
142
tree.relpath(pathjoin(getcwd(), 'child')))
144
def test_lock_locks_branch(self):
145
tree = self.make_branch_and_tree('.')
147
self.assertEqual('r', tree.branch.peek_lock_mode())
149
self.assertEqual(None, tree.branch.peek_lock_mode())
151
self.assertEqual('w', tree.branch.peek_lock_mode())
153
self.assertEqual(None, tree.branch.peek_lock_mode())
155
def test_revert(self):
156
"""Test selected-file revert"""
157
tree = self.make_branch_and_tree('.')
159
self.build_tree(['hello.txt'])
160
file('hello.txt', 'w').write('initial hello')
162
self.assertRaises(PathsNotVersionedError,
163
tree.revert, ['hello.txt'])
164
tree.add(['hello.txt'])
165
tree.commit('create initial hello.txt')
167
self.check_file_contents('hello.txt', 'initial hello')
168
file('hello.txt', 'w').write('new hello')
169
self.check_file_contents('hello.txt', 'new hello')
171
# revert file modified since last revision
172
tree.revert(['hello.txt'])
173
self.check_file_contents('hello.txt', 'initial hello')
174
self.check_file_contents('hello.txt.~1~', 'new hello')
176
# reverting again does not clobber the backup
177
tree.revert(['hello.txt'])
178
self.check_file_contents('hello.txt', 'initial hello')
179
self.check_file_contents('hello.txt.~1~', 'new hello')
181
# backup files are numbered
182
file('hello.txt', 'w').write('new hello2')
183
tree.revert(['hello.txt'])
184
self.check_file_contents('hello.txt', 'initial hello')
185
self.check_file_contents('hello.txt.~1~', 'new hello')
186
self.check_file_contents('hello.txt.~2~', 'new hello2')
188
def test_revert_missing(self):
189
# Revert a file that has been deleted since last commit
190
tree = self.make_branch_and_tree('.')
191
file('hello.txt', 'w').write('initial hello')
192
tree.add('hello.txt')
193
tree.commit('added hello.txt')
194
os.unlink('hello.txt')
195
tree.remove('hello.txt')
196
tree.revert(['hello.txt'])
197
self.failUnlessExists('hello.txt')
199
def test_versioned_files_not_unknown(self):
200
tree = self.make_branch_and_tree('.')
201
self.build_tree(['hello.txt'])
202
tree.add('hello.txt')
203
self.assertEquals(list(tree.unknowns()),
206
def test_unknowns(self):
207
tree = self.make_branch_and_tree('.')
208
self.build_tree(['hello.txt',
210
self.build_tree_contents([('.bzrignore', '*.~*\n')])
211
tree.add('.bzrignore')
212
self.assertEquals(list(tree.unknowns()),
215
def test_initialize(self):
216
# initialize should create a working tree and branch in an existing dir
217
t = self.make_branch_and_tree('.')
218
b = branch.Branch.open('.')
219
self.assertEqual(t.branch.base, b.base)
220
t2 = WorkingTree.open('.')
221
self.assertEqual(t.basedir, t2.basedir)
222
self.assertEqual(b.base, t2.branch.base)
223
# TODO maybe we should check the branch format? not sure if its
226
def test_rename_dirs(self):
227
"""Test renaming directories and the files within them."""
228
wt = self.make_branch_and_tree('.')
230
self.build_tree(['dir/', 'dir/sub/', 'dir/sub/file'])
231
wt.add(['dir', 'dir/sub', 'dir/sub/file'])
233
wt.commit('create initial state')
235
revid = b.revision_history()[0]
236
self.log('first revision_id is {%s}' % revid)
238
inv = b.repository.get_revision_inventory(revid)
239
self.log('contents of inventory: %r' % inv.entries())
241
self.check_inventory_shape(inv,
242
['dir/', 'dir/sub/', 'dir/sub/file'])
243
wt.rename_one('dir', 'newdir')
246
self.check_inventory_shape(wt.inventory,
247
['newdir/', 'newdir/sub/', 'newdir/sub/file'])
249
wt.rename_one('newdir/sub', 'newdir/newsub')
251
self.check_inventory_shape(wt.inventory,
252
['newdir/', 'newdir/newsub/',
253
'newdir/newsub/file'])
256
def test_add_in_unversioned(self):
257
"""Try to add a file in an unversioned directory.
259
"bzr add" adds the parent as necessary, but simple working tree add
262
from bzrlib.errors import NotVersionedError
263
wt = self.make_branch_and_tree('.')
264
self.build_tree(['foo/',
266
self.assertRaises(NotVersionedError,
270
def test_add_missing(self):
271
# adding a msising file -> NoSuchFile
272
wt = self.make_branch_and_tree('.')
273
self.assertRaises(errors.NoSuchFile, wt.add, 'fpp')
275
def test_remove_verbose(self):
276
#FIXME the remove api should not print or otherwise depend on the
277
# text UI - RBC 20060124
278
wt = self.make_branch_and_tree('.')
279
self.build_tree(['hello'])
281
wt.commit(message='add hello')
284
self.assertEqual(None, self.apply_redirected(None, stdout, stderr,
288
self.assertEqual('? hello\n', stdout.getvalue())
289
self.assertEqual('', stderr.getvalue())
291
def test_clone_trivial(self):
292
wt = self.make_branch_and_tree('source')
293
cloned_dir = wt.bzrdir.clone('target')
294
cloned = cloned_dir.open_workingtree()
295
self.assertEqual(cloned.get_parent_ids(), wt.get_parent_ids())
297
def test_last_revision(self):
298
wt = self.make_branch_and_tree('source')
299
self.assertEqual([], wt.get_parent_ids())
300
wt.commit('A', allow_pointless=True, rev_id='A')
301
parent_ids = wt.get_parent_ids()
302
self.assertEqual(['A'], parent_ids)
303
for parent_id in parent_ids:
304
self.assertIsInstance(parent_id, str)
306
def test_set_last_revision(self):
307
wt = self.make_branch_and_tree('source')
308
# set last-revision to one not in the history
309
wt.set_last_revision('A')
310
# set it back to None for an empty tree.
311
wt.set_last_revision('null:')
312
wt.commit('A', allow_pointless=True, rev_id='A')
313
self.assertEqual(['A'], wt.get_parent_ids())
314
# None is aways in the branch
315
wt.set_last_revision('null:')
316
self.assertEqual([], wt.get_parent_ids())
317
# and now we can set it to 'A'
318
# because some formats mutate the branch to set it on the tree
319
# we need to alter the branch to let this pass.
321
wt.branch.set_revision_history(['A', 'B'])
322
except errors.NoSuchRevision, e:
323
self.assertEqual('B', e.revision)
324
raise TestSkipped("Branch format does not permit arbitrary"
326
wt.set_last_revision('A')
327
self.assertEqual(['A'], wt.get_parent_ids())
328
self.assertRaises(errors.ReservedId, wt.set_last_revision, 'A:')
330
def test_set_last_revision_different_to_branch(self):
331
# working tree formats from the meta-dir format and newer support
332
# setting the last revision on a tree independently of that on the
333
# branch. Its concievable that some future formats may want to
334
# couple them again (i.e. because its really a smart server and
335
# the working tree will always match the branch). So we test
336
# that formats where initialising a branch does not initialise a
337
# tree - and thus have separable entities - support skewing the
339
branch = self.make_branch('tree')
341
# if there is a working tree now, this is not supported.
342
branch.bzrdir.open_workingtree()
344
except errors.NoWorkingTree:
346
wt = branch.bzrdir.create_workingtree()
347
wt.commit('A', allow_pointless=True, rev_id='A')
348
wt.set_last_revision(None)
349
self.assertEqual([], wt.get_parent_ids())
350
self.assertEqual('A', wt.branch.last_revision())
351
# and now we can set it back to 'A'
352
wt.set_last_revision('A')
353
self.assertEqual(['A'], wt.get_parent_ids())
354
self.assertEqual('A', wt.branch.last_revision())
356
def test_clone_and_commit_preserves_last_revision(self):
357
"""Doing a commit into a clone tree does not affect the source."""
358
wt = self.make_branch_and_tree('source')
359
cloned_dir = wt.bzrdir.clone('target')
360
wt.commit('A', allow_pointless=True, rev_id='A')
361
self.assertNotEqual(cloned_dir.open_workingtree().get_parent_ids(),
364
def test_clone_preserves_content(self):
365
wt = self.make_branch_and_tree('source')
366
self.build_tree(['added', 'deleted', 'notadded'],
367
transport=wt.bzrdir.transport.clone('..'))
368
wt.add('deleted', 'deleted')
369
wt.commit('add deleted')
371
wt.add('added', 'added')
372
cloned_dir = wt.bzrdir.clone('target')
373
cloned = cloned_dir.open_workingtree()
374
cloned_transport = cloned.bzrdir.transport.clone('..')
375
self.assertFalse(cloned_transport.has('deleted'))
376
self.assertTrue(cloned_transport.has('added'))
377
self.assertFalse(cloned_transport.has('notadded'))
378
self.assertEqual('added', cloned.path2id('added'))
379
self.assertEqual(None, cloned.path2id('deleted'))
380
self.assertEqual(None, cloned.path2id('notadded'))
382
def test_basis_tree_returns_last_revision(self):
383
wt = self.make_branch_and_tree('.')
384
self.build_tree(['foo'])
385
wt.add('foo', 'foo-id')
386
wt.commit('A', rev_id='A')
387
wt.rename_one('foo', 'bar')
388
wt.commit('B', rev_id='B')
389
wt.set_parent_ids(['B'])
390
tree = wt.basis_tree()
392
self.failUnless(tree.has_filename('bar'))
394
wt.set_parent_ids(['A'])
395
tree = wt.basis_tree()
397
self.failUnless(tree.has_filename('foo'))
400
def test_clone_tree_revision(self):
401
# make a tree with a last-revision,
402
# and clone it with a different last-revision, this should switch
405
# also test that the content is merged
406
# and conflicts recorded.
407
# This should merge between the trees - local edits should be preserved
408
# but other changes occured.
409
# we test this by having one file that does
410
# not change between two revisions, and another that does -
411
# if the changed one is not changed, fail,
412
# if the one that did not change has lost a local change, fail.
414
raise TestSkipped('revision limiting is not implemented yet.')
416
def test_initialize_with_revision_id(self):
417
# a bzrdir can construct a working tree for itself @ a specific revision.
418
source = self.make_branch_and_tree('source')
419
source.commit('a', rev_id='a', allow_pointless=True)
420
source.commit('b', rev_id='b', allow_pointless=True)
421
self.build_tree(['new/'])
422
made_control = self.bzrdir_format.initialize('new')
423
source.branch.repository.clone(made_control)
424
source.branch.clone(made_control)
425
made_tree = self.workingtree_format.initialize(made_control, revision_id='a')
426
self.assertEqual(['a'], made_tree.get_parent_ids())
428
def test_update_sets_last_revision(self):
429
# working tree formats from the meta-dir format and newer support
430
# setting the last revision on a tree independently of that on the
431
# branch. Its concievable that some future formats may want to
432
# couple them again (i.e. because its really a smart server and
433
# the working tree will always match the branch). So we test
434
# that formats where initialising a branch does not initialise a
435
# tree - and thus have separable entities - support skewing the
437
main_branch = self.make_branch('tree')
439
# if there is a working tree now, this is not supported.
440
main_branch.bzrdir.open_workingtree()
442
except errors.NoWorkingTree:
444
wt = main_branch.bzrdir.create_workingtree()
445
# create an out of date working tree by making a checkout in this
447
self.build_tree(['checkout/', 'tree/file'])
448
checkout = bzrdir.BzrDirMetaFormat1().initialize('checkout')
449
branch.BranchReferenceFormat().initialize(checkout, main_branch)
450
old_tree = self.workingtree_format.initialize(checkout)
451
# now commit to 'tree'
453
wt.commit('A', rev_id='A')
454
# and update old_tree
455
self.assertEqual(0, old_tree.update())
456
self.failUnlessExists('checkout/file')
457
self.assertEqual(['A'], old_tree.get_parent_ids())
459
def test_update_sets_root_id(self):
460
"""Ensure tree root is set properly by update.
462
Since empty trees don't have root_ids, but workingtrees do,
463
an update of a checkout of revision 0 to a new revision, should set
466
wt = self.make_branch_and_tree('tree')
467
main_branch = wt.branch
468
# create an out of date working tree by making a checkout in this
470
self.build_tree(['checkout/', 'tree/file'])
471
checkout = main_branch.create_checkout('checkout')
472
# now commit to 'tree'
474
wt.commit('A', rev_id='A')
475
# and update checkout
476
self.assertEqual(0, checkout.update())
477
self.failUnlessExists('checkout/file')
478
self.assertEqual(wt.get_root_id(), checkout.get_root_id())
479
self.assertNotEqual(None, wt.get_root_id())
481
def test_update_returns_conflict_count(self):
482
# working tree formats from the meta-dir format and newer support
483
# setting the last revision on a tree independently of that on the
484
# branch. Its concievable that some future formats may want to
485
# couple them again (i.e. because its really a smart server and
486
# the working tree will always match the branch). So we test
487
# that formats where initialising a branch does not initialise a
488
# tree - and thus have separable entities - support skewing the
490
main_branch = self.make_branch('tree')
492
# if there is a working tree now, this is not supported.
493
main_branch.bzrdir.open_workingtree()
495
except errors.NoWorkingTree:
497
wt = main_branch.bzrdir.create_workingtree()
498
# create an out of date working tree by making a checkout in this
500
self.build_tree(['checkout/', 'tree/file'])
501
checkout = bzrdir.BzrDirMetaFormat1().initialize('checkout')
502
branch.BranchReferenceFormat().initialize(checkout, main_branch)
503
old_tree = self.workingtree_format.initialize(checkout)
504
# now commit to 'tree'
506
wt.commit('A', rev_id='A')
507
# and add a file file to the checkout
508
self.build_tree(['checkout/file'])
510
# and update old_tree
511
self.assertEqual(1, old_tree.update())
512
self.assertEqual(['A'], old_tree.get_parent_ids())
514
def test_merge_revert(self):
515
from bzrlib.merge import merge_inner
516
this = self.make_branch_and_tree('b1')
517
open('b1/a', 'wb').write('a test\n')
519
open('b1/b', 'wb').write('b test\n')
521
this.commit(message='')
522
base = this.bzrdir.clone('b2').open_workingtree()
523
open('b2/a', 'wb').write('b test\n')
524
other = this.bzrdir.clone('b3').open_workingtree()
525
open('b3/a', 'wb').write('c test\n')
526
open('b3/c', 'wb').write('c test\n')
529
open('b1/b', 'wb').write('q test\n')
530
open('b1/d', 'wb').write('d test\n')
531
merge_inner(this.branch, other, base, this_tree=this)
532
self.assertNotEqual(open('b1/a', 'rb').read(), 'a test\n')
534
self.assertEqual(open('b1/a', 'rb').read(), 'a test\n')
535
self.assertIs(os.path.exists('b1/b.~1~'), True)
536
self.assertIs(os.path.exists('b1/c'), False)
537
self.assertIs(os.path.exists('b1/a.~1~'), False)
538
self.assertIs(os.path.exists('b1/d'), True)
540
def test_update_updates_bound_branch_no_local_commits(self):
541
# doing an update in a tree updates the branch its bound to too.
542
master_tree = self.make_branch_and_tree('master')
543
tree = self.make_branch_and_tree('tree')
545
tree.branch.bind(master_tree.branch)
546
except errors.UpgradeRequired:
547
# legacy branches cannot bind
549
master_tree.commit('foo', rev_id='foo', allow_pointless=True)
551
self.assertEqual(['foo'], tree.get_parent_ids())
552
self.assertEqual('foo', tree.branch.last_revision())
554
def test_update_turns_local_commit_into_merge(self):
555
# doing an update with a few local commits and no master commits
556
# makes pending-merges.
557
# this is done so that 'bzr update; bzr revert' will always produce
558
# an exact copy of the 'logical branch' - the referenced branch for
559
# a checkout, and the master for a bound branch.
560
# its possible that we should instead have 'bzr update' when there
561
# is nothing new on the master leave the current commits intact and
562
# alter 'revert' to revert to the master always. But for now, its
564
master_tree = self.make_branch_and_tree('master')
565
master_tip = master_tree.commit('first master commit')
566
tree = self.make_branch_and_tree('tree')
568
tree.branch.bind(master_tree.branch)
569
except errors.UpgradeRequired:
570
# legacy branches cannot bind
575
tree.commit('foo', rev_id='foo', allow_pointless=True, local=True)
576
tree.commit('bar', rev_id='bar', allow_pointless=True, local=True)
577
# sync with master prepatory to committing
579
# which should have pivoted the local tip into a merge
580
self.assertEqual([master_tip, 'bar'], tree.get_parent_ids())
581
# and the local branch history should match the masters now.
582
self.assertEqual(master_tree.branch.revision_history(),
583
tree.branch.revision_history())
585
def test_merge_modified_detects_corruption(self):
586
# FIXME: This doesn't really test that it works; also this is not
587
# implementation-independent. mbp 20070226
588
tree = self.make_branch_and_tree('master')
589
tree._transport.put_bytes('merge-hashes', 'asdfasdf')
590
self.assertRaises(errors.MergeModifiedFormatError, tree.merge_modified)
592
def test_merge_modified(self):
593
# merge_modified stores a map from file id to hash
594
tree = self.make_branch_and_tree('tree')
595
d = {'file-id': osutils.sha_string('hello')}
596
self.build_tree_contents([('tree/somefile', 'hello')])
599
tree.add(['somefile'], ['file-id'])
600
tree.set_merge_modified(d)
601
mm = tree.merge_modified()
602
self.assertEquals(mm, d)
605
mm = tree.merge_modified()
606
self.assertEquals(mm, d)
608
def test_conflicts(self):
609
from bzrlib.tests.test_conflicts import example_conflicts
610
tree = self.make_branch_and_tree('master')
612
tree.set_conflicts(example_conflicts)
613
except UnsupportedOperation:
614
raise TestSkipped('set_conflicts not supported')
616
tree2 = WorkingTree.open('master')
617
self.assertEqual(tree2.conflicts(), example_conflicts)
618
tree2._transport.put_bytes('conflicts', '')
619
self.assertRaises(errors.ConflictFormatError,
621
tree2._transport.put_bytes('conflicts', 'a')
622
self.assertRaises(errors.ConflictFormatError,
625
def make_merge_conflicts(self):
626
from bzrlib.merge import merge_inner
627
tree = self.make_branch_and_tree('mine')
628
file('mine/bloo', 'wb').write('one')
629
file('mine/blo', 'wb').write('on')
630
tree.add(['bloo', 'blo'])
631
tree.commit("blah", allow_pointless=False)
632
base = tree.branch.repository.revision_tree(tree.last_revision())
633
bzrdir.BzrDir.open("mine").sprout("other")
634
file('other/bloo', 'wb').write('two')
635
othertree = WorkingTree.open('other')
636
othertree.commit('blah', allow_pointless=False)
637
file('mine/bloo', 'wb').write('three')
638
tree.commit("blah", allow_pointless=False)
639
merge_inner(tree.branch, othertree, base, this_tree=tree)
642
def test_merge_conflicts(self):
643
tree = self.make_merge_conflicts()
644
self.assertEqual(len(tree.conflicts()), 1)
646
def test_clear_merge_conflicts(self):
647
tree = self.make_merge_conflicts()
648
self.assertEqual(len(tree.conflicts()), 1)
650
tree.set_conflicts(ConflictList())
651
except UnsupportedOperation:
652
raise TestSkipped('unsupported operation')
653
self.assertEqual(tree.conflicts(), ConflictList())
655
def test_add_conflicts(self):
656
tree = self.make_branch_and_tree('tree')
658
tree.add_conflicts([TextConflict('path_a')])
659
except UnsupportedOperation:
660
raise TestSkipped('unsupported operation')
661
self.assertEqual(ConflictList([TextConflict('path_a')]),
663
tree.add_conflicts([TextConflict('path_a')])
664
self.assertEqual(ConflictList([TextConflict('path_a')]),
666
tree.add_conflicts([ContentsConflict('path_a')])
667
self.assertEqual(ConflictList([ContentsConflict('path_a'),
668
TextConflict('path_a')]),
670
tree.add_conflicts([TextConflict('path_b')])
671
self.assertEqual(ConflictList([ContentsConflict('path_a'),
672
TextConflict('path_a'),
673
TextConflict('path_b')]),
676
def test_revert_clear_conflicts(self):
677
tree = self.make_merge_conflicts()
678
self.assertEqual(len(tree.conflicts()), 1)
680
self.assertEqual(len(tree.conflicts()), 1)
681
tree.revert(["bloo"])
682
self.assertEqual(len(tree.conflicts()), 0)
684
def test_revert_clear_conflicts2(self):
685
tree = self.make_merge_conflicts()
686
self.assertEqual(len(tree.conflicts()), 1)
688
self.assertEqual(len(tree.conflicts()), 0)
690
def test_format_description(self):
691
tree = self.make_branch_and_tree('tree')
692
text = tree._format.get_format_description()
693
self.failUnless(len(text))
695
def test_branch_attribute_is_not_settable(self):
696
# the branch attribute is an aspect of the working tree, not a
697
# configurable attribute
698
tree = self.make_branch_and_tree('tree')
700
tree.branch = tree.branch
701
self.assertRaises(AttributeError, set_branch)
703
def test_list_files_versioned_before_ignored(self):
704
"""A versioned file matching an ignore rule should not be ignored."""
705
tree = self.make_branch_and_tree('.')
706
self.build_tree(['foo.pyc'])
707
# ensure that foo.pyc is ignored
708
self.build_tree_contents([('.bzrignore', 'foo.pyc')])
709
tree.add('foo.pyc', 'anid')
711
files = sorted(list(tree.list_files()))
713
self.assertEqual((u'.bzrignore', '?', 'file', None), files[0][:-1])
714
self.assertEqual((u'foo.pyc', 'V', 'file', 'anid'), files[1][:-1])
715
self.assertEqual(2, len(files))
717
def test_non_normalized_add_accessible(self):
719
self.build_tree([u'a\u030a'])
721
raise TestSkipped('Filesystem does not support unicode filenames')
722
tree = self.make_branch_and_tree('.')
723
orig = osutils.normalized_filename
724
osutils.normalized_filename = osutils._accessible_normalized_filename
726
tree.add([u'a\u030a'])
728
self.assertEqual([('', 'directory'), (u'\xe5', 'file')],
729
[(path, ie.kind) for path,ie in
730
tree.inventory.iter_entries()])
733
osutils.normalized_filename = orig
735
def test_non_normalized_add_inaccessible(self):
737
self.build_tree([u'a\u030a'])
739
raise TestSkipped('Filesystem does not support unicode filenames')
740
tree = self.make_branch_and_tree('.')
741
orig = osutils.normalized_filename
742
osutils.normalized_filename = osutils._inaccessible_normalized_filename
744
self.assertRaises(errors.InvalidNormalization,
745
tree.add, [u'a\u030a'])
747
osutils.normalized_filename = orig
749
def test__write_inventory(self):
750
# The private interface _write_inventory is currently used by transform.
751
tree = self.make_branch_and_tree('.')
752
# if we write write an inventory then do a walkdirs we should get back
753
# missing entries, and actual, and unknowns as appropriate.
754
self.build_tree(['present', 'unknown'])
755
inventory = Inventory(tree.get_root_id())
756
inventory.add_path('missing', 'file', 'missing-id')
757
inventory.add_path('present', 'file', 'present-id')
758
# there is no point in being able to write an inventory to an unlocked
759
# tree object - its a low level api not a convenience api.
761
tree._write_inventory(inventory)
765
present_stat = os.lstat('present')
766
unknown_stat = os.lstat('unknown')
768
(('', tree.get_root_id()),
769
[('missing', 'missing', 'unknown', None, 'missing-id', 'file'),
770
('present', 'present', 'file', present_stat, 'present-id', 'file'),
771
('unknown', 'unknown', 'file', unknown_stat, None, None),
774
self.assertEqual(expected_results, list(tree.walkdirs()))
778
def test_path2id(self):
779
# smoke test for path2id
780
tree = self.make_branch_and_tree('.')
781
self.build_tree(['foo'])
782
tree.add(['foo'], ['foo-id'])
783
self.assertEqual('foo-id', tree.path2id('foo'))
784
# the next assertion is for backwards compatability with WorkingTree3,
785
# though its probably a bad idea, it makes things work. Perhaps
786
# it should raise a deprecation warning?
787
self.assertEqual('foo-id', tree.path2id('foo/'))
789
def test_filter_unversioned_files(self):
790
# smoke test for filter_unversioned_files
791
tree = self.make_branch_and_tree('.')
792
paths = ['here-and-versioned', 'here-and-not-versioned',
793
'not-here-and-versioned', 'not-here-and-not-versioned']
794
tree.add(['here-and-versioned', 'not-here-and-versioned'],
795
kinds=['file', 'file'])
796
self.build_tree(['here-and-versioned', 'here-and-not-versioned'])
798
self.addCleanup(tree.unlock)
800
set(['not-here-and-not-versioned', 'here-and-not-versioned']),
801
tree.filter_unversioned_files(paths))
803
def test_detect_real_kind(self):
804
# working trees report the real kind of the file on disk, not the kind
805
# they had when they were first added
806
# create one file of every interesting type
807
tree = self.make_branch_and_tree('.')
808
self.build_tree(['file', 'directory/'])
809
names = ['file', 'directory']
811
os.symlink('target', 'symlink')
812
names.append('symlink')
813
tree.add(names, [n + '-id' for n in names])
814
if tree.supports_tree_reference():
815
sub_tree = self.make_branch_and_tree('tree-reference')
816
sub_tree.set_root_id('tree-reference-id')
817
sub_tree.commit('message')
818
names.append('tree-reference')
819
tree.add_reference(sub_tree)
820
# now when we first look, we should see everything with the same kind
821
# with which they were initially added
823
actual_kind = tree.kind(n + '-id')
824
self.assertEqual(n, actual_kind)
825
# move them around so the names no longer correspond to the types
826
os.rename(names[0], 'tmp')
827
for i in range(1, len(names)):
828
os.rename(names[i], names[i-1])
829
os.rename('tmp', names[-1])
830
# now look and expect to see the correct types again
831
for i in range(len(names)):
832
actual_kind = tree.kind(names[i-1] + '-id')
833
expected_kind = names[i]
834
self.assertEqual(expected_kind, actual_kind)
836
def test_stored_kind_with_missing(self):
837
tree = self.make_branch_and_tree('tree')
839
self.addCleanup(tree.unlock)
840
self.build_tree(['tree/a', 'tree/b/'])
841
tree.add(['a', 'b'], ['a-id', 'b-id'])
844
self.assertEqual('file', tree.stored_kind('a-id'))
845
self.assertEqual('directory', tree.stored_kind('b-id'))
847
def test_missing_file_sha1(self):
848
"""If a file is missing, its sha1 should be reported as None."""
849
tree = self.make_branch_and_tree('.')
851
self.addCleanup(tree.unlock)
852
self.build_tree(['file'])
853
tree.add('file', 'file-id')
854
tree.commit('file added')
856
self.assertIs(None, tree.get_file_sha1('file-id'))
858
def test_no_file_sha1(self):
859
"""If a file is not present, get_file_sha1 should raise NoSuchId"""
860
tree = self.make_branch_and_tree('.')
862
self.addCleanup(tree.unlock)
863
self.assertRaises(errors.NoSuchId, tree.get_file_sha1, 'file-id')
864
self.build_tree(['file'])
865
tree.add('file', 'file-id')
868
self.assertRaises(errors.NoSuchId, tree.get_file_sha1, 'file-id')
870
def test_case_sensitive(self):
871
"""If filesystem is case-sensitive, tree should report this.
873
We check case-sensitivity by creating a file with a lowercase name,
874
then testing whether it exists with an uppercase name.
876
self.build_tree(['filename'])
877
if os.path.exists('FILENAME'):
878
case_sensitive = False
880
case_sensitive = True
881
tree = self.make_branch_and_tree('test')
882
if tree.__class__ == WorkingTree2:
883
raise TestSkipped('WorkingTree2 is not supported')
884
self.assertEqual(case_sensitive, tree.case_sensitive)
886
def test_all_file_ids_with_missing(self):
887
tree = self.make_branch_and_tree('tree')
889
self.addCleanup(tree.unlock)
890
self.build_tree(['tree/a', 'tree/b'])
891
tree.add(['a', 'b'], ['a-id', 'b-id'])
893
self.assertEqual(set(['a-id', 'b-id', tree.get_root_id()]),
896
def test_sprout_hardlink(self):
897
real_os_link = getattr(os, 'link', None)
898
if real_os_link is None:
899
raise TestNotApplicable("This platform doesn't provide os.link")
900
source = self.make_branch_and_tree('source')
901
self.build_tree(['source/file'])
903
source.commit('added file')
904
def fake_link(source, target):
905
raise OSError(errno.EPERM, 'Operation not permitted')
908
# Hard-link support is optional, so supplying hardlink=True may
909
# or may not raise an exception. But if it does, it must be
910
# HardLinkNotSupported
912
source.bzrdir.sprout('target', accelerator_tree=source,
914
except errors.HardLinkNotSupported:
917
os.link = real_os_link
920
class TestIllegalPaths(TestCaseWithWorkingTree):
922
def test_bad_fs_path(self):
923
if osutils.normalizes_filenames():
924
# You *can't* create an illegal filename on OSX.
925
raise tests.TestNotApplicable('OSX normalizes filenames')
926
self.requireFeature(tests.UTF8Filesystem)
927
# We require a UTF8 filesystem, because otherwise we would need to get
928
# tricky to figure out how to create an illegal filename.
929
# \xb5 is an illegal path because it should be \xc2\xb5 for UTF-8
930
tree = self.make_branch_and_tree('tree')
931
self.build_tree(['tree/subdir/'])
934
f = open('tree/subdir/m\xb5', 'wb')
941
self.addCleanup(tree.unlock)
942
basis = tree.basis_tree()
944
self.addCleanup(basis.unlock)
946
e = self.assertListRaises(errors.BadFilenameEncoding,
947
tree.iter_changes, tree.basis_tree(),
948
want_unversioned=True)
949
# We should display the relative path
950
self.assertEqual('subdir/m\xb5', e.filename)
951
self.assertEqual(osutils._fs_enc, e.fs_encoding)