summaryrefslogtreecommitdiff
path: root/bzrlib/tests/per_workingtree/test_workingtree.py
diff options
context:
space:
mode:
Diffstat (limited to 'bzrlib/tests/per_workingtree/test_workingtree.py')
-rw-r--r--bzrlib/tests/per_workingtree/test_workingtree.py1234
1 files changed, 1234 insertions, 0 deletions
diff --git a/bzrlib/tests/per_workingtree/test_workingtree.py b/bzrlib/tests/per_workingtree/test_workingtree.py
new file mode 100644
index 0000000..4177eae
--- /dev/null
+++ b/bzrlib/tests/per_workingtree/test_workingtree.py
@@ -0,0 +1,1234 @@
+# Copyright (C) 2006-2011 Canonical Ltd
+# Authors: Robert Collins <robert.collins@canonical.com>
+# and others
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+from cStringIO import StringIO
+import errno
+import os
+
+from bzrlib import (
+ branch,
+ bzrdir,
+ config,
+ controldir,
+ errors,
+ osutils,
+ revision as _mod_revision,
+ symbol_versioning,
+ tests,
+ trace,
+ urlutils,
+ )
+from bzrlib.errors import (
+ UnsupportedOperation,
+ PathsNotVersionedError,
+ )
+from bzrlib.inventory import Inventory
+from bzrlib.mutabletree import MutableTree
+from bzrlib.osutils import pathjoin, getcwd, has_symlinks
+from bzrlib.tests import (
+ features,
+ TestSkipped,
+ TestNotApplicable,
+ )
+from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree
+from bzrlib.workingtree import (
+ TreeDirectory,
+ TreeFile,
+ TreeLink,
+ InventoryWorkingTree,
+ WorkingTree,
+ )
+from bzrlib.conflicts import ConflictList, TextConflict, ContentsConflict
+
+
+class TestWorkingTree(TestCaseWithWorkingTree):
+
+ def test_branch_builder(self):
+ # Just a smoke test that we get a branch at the specified relpath
+ builder = self.make_branch_builder('foobar')
+ br = branch.Branch.open('foobar')
+
+ def test_list_files(self):
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['dir/', 'file'])
+ if has_symlinks():
+ os.symlink('target', 'symlink')
+ tree.lock_read()
+ files = list(tree.list_files())
+ tree.unlock()
+ self.assertEqual(files[0], ('dir', '?', 'directory', None, TreeDirectory()))
+ self.assertEqual(files[1], ('file', '?', 'file', None, TreeFile()))
+ if has_symlinks():
+ self.assertEqual(files[2], ('symlink', '?', 'symlink', None, TreeLink()))
+
+ def test_list_files_sorted(self):
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['dir/', 'file', 'dir/file', 'dir/b',
+ 'dir/subdir/', 'a', 'dir/subfile',
+ 'zz_dir/', 'zz_dir/subfile'])
+ tree.lock_read()
+ files = [(path, kind) for (path, v, kind, file_id, entry)
+ in tree.list_files()]
+ tree.unlock()
+ self.assertEqual([
+ ('a', 'file'),
+ ('dir', 'directory'),
+ ('file', 'file'),
+ ('zz_dir', 'directory'),
+ ], files)
+
+ tree.add(['dir', 'zz_dir'])
+ tree.lock_read()
+ files = [(path, kind) for (path, v, kind, file_id, entry)
+ in tree.list_files()]
+ tree.unlock()
+ self.assertEqual([
+ ('a', 'file'),
+ ('dir', 'directory'),
+ ('dir/b', 'file'),
+ ('dir/file', 'file'),
+ ('dir/subdir', 'directory'),
+ ('dir/subfile', 'file'),
+ ('file', 'file'),
+ ('zz_dir', 'directory'),
+ ('zz_dir/subfile', 'file'),
+ ], files)
+
+ def test_list_files_kind_change(self):
+ tree = self.make_branch_and_tree('tree')
+ self.build_tree(['tree/filename'])
+ tree.add('filename', 'file-id')
+ os.unlink('tree/filename')
+ self.build_tree(['tree/filename/'])
+ tree.lock_read()
+ self.addCleanup(tree.unlock)
+ result = list(tree.list_files())
+ self.assertEqual(1, len(result))
+ self.assertEqual(('filename', 'V', 'directory', 'file-id'),
+ result[0][:4])
+
+ def test_get_config_stack(self):
+ # Smoke test that all working trees succeed getting a config
+ wt = self.make_branch_and_tree('.')
+ conf = wt.get_config_stack()
+ self.assertIsInstance(conf, config.Stack)
+
+ def test_open_containing(self):
+ branch = self.make_branch_and_tree('.').branch
+ local_base = urlutils.local_path_from_url(branch.base)
+
+ # Empty opens '.'
+ wt, relpath = WorkingTree.open_containing()
+ self.assertEqual('', relpath)
+ self.assertEqual(wt.basedir + '/', local_base)
+
+ # '.' opens this dir
+ wt, relpath = WorkingTree.open_containing(u'.')
+ self.assertEqual('', relpath)
+ self.assertEqual(wt.basedir + '/', local_base)
+
+ # './foo' finds '.' and a relpath of 'foo'
+ wt, relpath = WorkingTree.open_containing('./foo')
+ self.assertEqual('foo', relpath)
+ self.assertEqual(wt.basedir + '/', local_base)
+
+ # abspath(foo) finds '.' and relpath of 'foo'
+ wt, relpath = WorkingTree.open_containing('./foo')
+ wt, relpath = WorkingTree.open_containing(getcwd() + '/foo')
+ self.assertEqual('foo', relpath)
+ self.assertEqual(wt.basedir + '/', local_base)
+
+ # can even be a url: finds '.' and relpath of 'foo'
+ wt, relpath = WorkingTree.open_containing('./foo')
+ wt, relpath = WorkingTree.open_containing(
+ urlutils.local_path_to_url(getcwd() + '/foo'))
+ self.assertEqual('foo', relpath)
+ self.assertEqual(wt.basedir + '/', local_base)
+
+ def test_basic_relpath(self):
+ # for comprehensive relpath tests, see whitebox.py.
+ tree = self.make_branch_and_tree('.')
+ self.assertEqual('child',
+ tree.relpath(pathjoin(getcwd(), 'child')))
+
+ def test_lock_locks_branch(self):
+ tree = self.make_branch_and_tree('.')
+ tree.lock_read()
+ self.assertEqual('r', tree.branch.peek_lock_mode())
+ tree.unlock()
+ self.assertEqual(None, tree.branch.peek_lock_mode())
+ tree.lock_write()
+ self.assertEqual('w', tree.branch.peek_lock_mode())
+ tree.unlock()
+ self.assertEqual(None, tree.branch.peek_lock_mode())
+
+ def test_revert(self):
+ """Test selected-file revert"""
+ tree = self.make_branch_and_tree('.')
+
+ self.build_tree(['hello.txt'])
+ with file('hello.txt', 'w') as f: f.write('initial hello')
+
+ self.assertRaises(PathsNotVersionedError,
+ tree.revert, ['hello.txt'])
+ tree.add(['hello.txt'])
+ tree.commit('create initial hello.txt')
+
+ self.check_file_contents('hello.txt', 'initial hello')
+ with file('hello.txt', 'w') as f: f.write('new hello')
+ self.check_file_contents('hello.txt', 'new hello')
+
+ # revert file modified since last revision
+ tree.revert(['hello.txt'])
+ self.check_file_contents('hello.txt', 'initial hello')
+ self.check_file_contents('hello.txt.~1~', 'new hello')
+
+ # reverting again does not clobber the backup
+ tree.revert(['hello.txt'])
+ self.check_file_contents('hello.txt', 'initial hello')
+ self.check_file_contents('hello.txt.~1~', 'new hello')
+
+ # backup files are numbered
+ with file('hello.txt', 'w') as f: f.write('new hello2')
+ tree.revert(['hello.txt'])
+ self.check_file_contents('hello.txt', 'initial hello')
+ self.check_file_contents('hello.txt.~1~', 'new hello')
+ self.check_file_contents('hello.txt.~2~', 'new hello2')
+
+ def test_revert_missing(self):
+ # Revert a file that has been deleted since last commit
+ tree = self.make_branch_and_tree('.')
+ with file('hello.txt', 'w') as f: f.write('initial hello')
+ tree.add('hello.txt')
+ tree.commit('added hello.txt')
+ os.unlink('hello.txt')
+ tree.remove('hello.txt')
+ tree.revert(['hello.txt'])
+ self.assertPathExists('hello.txt')
+
+ def test_versioned_files_not_unknown(self):
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['hello.txt'])
+ tree.add('hello.txt')
+ self.assertEquals(list(tree.unknowns()),
+ [])
+
+ def test_unknowns(self):
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['hello.txt',
+ 'hello.txt.~1~'])
+ self.build_tree_contents([('.bzrignore', '*.~*\n')])
+ tree.add('.bzrignore')
+ self.assertEquals(list(tree.unknowns()),
+ ['hello.txt'])
+
+ def test_initialize(self):
+ # initialize should create a working tree and branch in an existing dir
+ t = self.make_branch_and_tree('.')
+ b = branch.Branch.open('.')
+ self.assertEqual(t.branch.base, b.base)
+ t2 = WorkingTree.open('.')
+ self.assertEqual(t.basedir, t2.basedir)
+ self.assertEqual(b.base, t2.branch.base)
+ # TODO maybe we should check the branch format? not sure if its
+ # appropriate here.
+
+ def test_rename_dirs(self):
+ """Test renaming directories and the files within them."""
+ wt = self.make_branch_and_tree('.')
+ b = wt.branch
+ self.build_tree(['dir/', 'dir/sub/', 'dir/sub/file'])
+ wt.add(['dir', 'dir/sub', 'dir/sub/file'])
+
+ wt.commit('create initial state')
+
+ revid = b.last_revision()
+ self.log('first revision_id is {%s}' % revid)
+
+ tree = b.repository.revision_tree(revid)
+ self.log('contents of tree: %r' % list(tree.iter_entries_by_dir()))
+
+ self.check_tree_shape(tree, ['dir/', 'dir/sub/', 'dir/sub/file'])
+ wt.rename_one('dir', 'newdir')
+
+ wt.lock_read()
+ self.check_tree_shape(wt,
+ ['newdir/', 'newdir/sub/', 'newdir/sub/file'])
+ wt.unlock()
+ wt.rename_one('newdir/sub', 'newdir/newsub')
+ wt.lock_read()
+ self.check_tree_shape(wt, ['newdir/', 'newdir/newsub/',
+ 'newdir/newsub/file'])
+ wt.unlock()
+
+ def test_add_in_unversioned(self):
+ """Try to add a file in an unversioned directory.
+
+ "bzr add" adds the parent as necessary, but simple working tree add
+ doesn't do that.
+ """
+ from bzrlib.errors import NotVersionedError
+ wt = self.make_branch_and_tree('.')
+ self.build_tree(['foo/',
+ 'foo/hello'])
+ if not wt._format.supports_versioned_directories:
+ wt.add('foo/hello')
+ else:
+ self.assertRaises(NotVersionedError,
+ wt.add,
+ 'foo/hello')
+
+ def test_add_missing(self):
+ # adding a msising file -> NoSuchFile
+ wt = self.make_branch_and_tree('.')
+ self.assertRaises(errors.NoSuchFile, wt.add, 'fpp')
+
+ def test_remove_verbose(self):
+ #FIXME the remove api should not print or otherwise depend on the
+ # text UI - RBC 20060124
+ wt = self.make_branch_and_tree('.')
+ self.build_tree(['hello'])
+ wt.add(['hello'])
+ wt.commit(message='add hello')
+ stdout = StringIO()
+ stderr = StringIO()
+ self.assertEqual(None, self.apply_redirected(None, stdout, stderr,
+ wt.remove,
+ ['hello'],
+ verbose=True))
+ self.assertEqual('? hello\n', stdout.getvalue())
+ self.assertEqual('', stderr.getvalue())
+
+ def test_clone_trivial(self):
+ wt = self.make_branch_and_tree('source')
+ cloned_dir = wt.bzrdir.clone('target')
+ cloned = cloned_dir.open_workingtree()
+ self.assertEqual(cloned.get_parent_ids(), wt.get_parent_ids())
+
+ def test_clone_empty(self):
+ wt = self.make_branch_and_tree('source')
+ cloned_dir = wt.bzrdir.clone('target', revision_id=_mod_revision.NULL_REVISION)
+ cloned = cloned_dir.open_workingtree()
+ self.assertEqual(cloned.get_parent_ids(), wt.get_parent_ids())
+
+ def test_last_revision(self):
+ wt = self.make_branch_and_tree('source')
+ self.assertEqual([], wt.get_parent_ids())
+ wt.commit('A', allow_pointless=True, rev_id='A')
+ parent_ids = wt.get_parent_ids()
+ self.assertEqual(['A'], parent_ids)
+ for parent_id in parent_ids:
+ self.assertIsInstance(parent_id, str)
+
+ def test_set_last_revision(self):
+ wt = self.make_branch_and_tree('source')
+ # set last-revision to one not in the history
+ wt.set_last_revision('A')
+ # set it back to None for an empty tree.
+ wt.set_last_revision('null:')
+ wt.commit('A', allow_pointless=True, rev_id='A')
+ self.assertEqual(['A'], wt.get_parent_ids())
+ # null: is aways in the branch
+ wt.set_last_revision('null:')
+ self.assertEqual([], wt.get_parent_ids())
+ # and now we can set it to 'A'
+ # because some formats mutate the branch to set it on the tree
+ # we need to alter the branch to let this pass.
+ if getattr(wt.branch, "_set_revision_history", None) is None:
+ raise TestSkipped("Branch format does not permit arbitrary"
+ " history")
+ wt.branch._set_revision_history(['A', 'B'])
+ wt.set_last_revision('A')
+ self.assertEqual(['A'], wt.get_parent_ids())
+ self.assertRaises(errors.ReservedId, wt.set_last_revision, 'A:')
+
+ def test_set_last_revision_different_to_branch(self):
+ # working tree formats from the meta-dir format and newer support
+ # setting the last revision on a tree independently of that on the
+ # branch. Its concievable that some future formats may want to
+ # couple them again (i.e. because its really a smart server and
+ # the working tree will always match the branch). So we test
+ # that formats where initialising a branch does not initialise a
+ # tree - and thus have separable entities - support skewing the
+ # two things.
+ branch = self.make_branch('tree')
+ try:
+ # if there is a working tree now, this is not supported.
+ branch.bzrdir.open_workingtree()
+ return
+ except errors.NoWorkingTree:
+ pass
+ wt = branch.bzrdir.create_workingtree()
+ wt.commit('A', allow_pointless=True, rev_id='A')
+ wt.set_last_revision(None)
+ self.assertEqual([], wt.get_parent_ids())
+ self.assertEqual('A', wt.branch.last_revision())
+ # and now we can set it back to 'A'
+ wt.set_last_revision('A')
+ self.assertEqual(['A'], wt.get_parent_ids())
+ self.assertEqual('A', wt.branch.last_revision())
+
+ def test_clone_and_commit_preserves_last_revision(self):
+ """Doing a commit into a clone tree does not affect the source."""
+ wt = self.make_branch_and_tree('source')
+ cloned_dir = wt.bzrdir.clone('target')
+ wt.commit('A', allow_pointless=True, rev_id='A')
+ self.assertNotEqual(cloned_dir.open_workingtree().get_parent_ids(),
+ wt.get_parent_ids())
+
+ def test_clone_preserves_content(self):
+ wt = self.make_branch_and_tree('source')
+ self.build_tree(['added', 'deleted', 'notadded'],
+ transport=wt.bzrdir.transport.clone('..'))
+ wt.add('deleted', 'deleted')
+ wt.commit('add deleted')
+ wt.remove('deleted')
+ wt.add('added', 'added')
+ cloned_dir = wt.bzrdir.clone('target')
+ cloned = cloned_dir.open_workingtree()
+ cloned_transport = cloned.bzrdir.transport.clone('..')
+ self.assertFalse(cloned_transport.has('deleted'))
+ self.assertTrue(cloned_transport.has('added'))
+ self.assertFalse(cloned_transport.has('notadded'))
+ self.assertEqual('added', cloned.path2id('added'))
+ self.assertEqual(None, cloned.path2id('deleted'))
+ self.assertEqual(None, cloned.path2id('notadded'))
+
+ def test_basis_tree_returns_last_revision(self):
+ wt = self.make_branch_and_tree('.')
+ self.build_tree(['foo'])
+ wt.add('foo', 'foo-id')
+ wt.commit('A', rev_id='A')
+ wt.rename_one('foo', 'bar')
+ wt.commit('B', rev_id='B')
+ wt.set_parent_ids(['B'])
+ tree = wt.basis_tree()
+ tree.lock_read()
+ self.assertTrue(tree.has_filename('bar'))
+ tree.unlock()
+ wt.set_parent_ids(['A'])
+ tree = wt.basis_tree()
+ tree.lock_read()
+ self.assertTrue(tree.has_filename('foo'))
+ tree.unlock()
+
+ def test_clone_tree_revision(self):
+ # make a tree with a last-revision,
+ # and clone it with a different last-revision, this should switch
+ # do it.
+ #
+ # also test that the content is merged
+ # and conflicts recorded.
+ # This should merge between the trees - local edits should be preserved
+ # but other changes occured.
+ # we test this by having one file that does
+ # not change between two revisions, and another that does -
+ # if the changed one is not changed, fail,
+ # if the one that did not change has lost a local change, fail.
+ #
+ raise TestSkipped('revision limiting is not implemented yet.')
+
+ def test_initialize_with_revision_id(self):
+ # a bzrdir can construct a working tree for itself @ a specific revision.
+ source = self.make_branch_and_tree('source')
+ source.commit('a', rev_id='a', allow_pointless=True)
+ source.commit('b', rev_id='b', allow_pointless=True)
+ self.build_tree(['new/'])
+ made_control = self.bzrdir_format.initialize('new')
+ source.branch.repository.clone(made_control)
+ source.branch.clone(made_control)
+ made_tree = self.workingtree_format.initialize(made_control,
+ revision_id='a')
+ self.assertEqual(['a'], made_tree.get_parent_ids())
+
+ def test_post_build_tree_hook(self):
+ calls = []
+ def track_post_build_tree(tree):
+ calls.append(tree.last_revision())
+ source = self.make_branch_and_tree('source')
+ source.commit('a', rev_id='a', allow_pointless=True)
+ source.commit('b', rev_id='b', allow_pointless=True)
+ self.build_tree(['new/'])
+ made_control = self.bzrdir_format.initialize('new')
+ source.branch.repository.clone(made_control)
+ source.branch.clone(made_control)
+ MutableTree.hooks.install_named_hook("post_build_tree",
+ track_post_build_tree, "Test")
+ made_tree = self.workingtree_format.initialize(made_control,
+ revision_id='a')
+ self.assertEqual(['a'], calls)
+
+ def test_update_sets_last_revision(self):
+ # working tree formats from the meta-dir format and newer support
+ # setting the last revision on a tree independently of that on the
+ # branch. Its concievable that some future formats may want to
+ # couple them again (i.e. because its really a smart server and
+ # the working tree will always match the branch). So we test
+ # that formats where initialising a branch does not initialise a
+ # tree - and thus have separable entities - support skewing the
+ # two things.
+ main_branch = self.make_branch('tree')
+ try:
+ # if there is a working tree now, this is not supported.
+ main_branch.bzrdir.open_workingtree()
+ return
+ except errors.NoWorkingTree:
+ pass
+ wt = main_branch.bzrdir.create_workingtree()
+ # create an out of date working tree by making a checkout in this
+ # current format
+ self.build_tree(['checkout/', 'tree/file'])
+ checkout = bzrdir.BzrDirMetaFormat1().initialize('checkout')
+ checkout.set_branch_reference(main_branch)
+ old_tree = self.workingtree_format.initialize(checkout)
+ # now commit to 'tree'
+ wt.add('file')
+ wt.commit('A', rev_id='A')
+ # and update old_tree
+ self.assertEqual(0, old_tree.update())
+ self.assertPathExists('checkout/file')
+ self.assertEqual(['A'], old_tree.get_parent_ids())
+
+ def test_update_sets_root_id(self):
+ """Ensure tree root is set properly by update.
+
+ Since empty trees don't have root_ids, but workingtrees do,
+ an update of a checkout of revision 0 to a new revision, should set
+ the root id.
+ """
+ wt = self.make_branch_and_tree('tree')
+ main_branch = wt.branch
+ # create an out of date working tree by making a checkout in this
+ # current format
+ self.build_tree(['checkout/', 'tree/file'])
+ checkout = main_branch.create_checkout('checkout')
+ # now commit to 'tree'
+ wt.add('file')
+ wt.commit('A', rev_id='A')
+ # and update checkout
+ self.assertEqual(0, checkout.update())
+ self.assertPathExists('checkout/file')
+ self.assertEqual(wt.get_root_id(), checkout.get_root_id())
+ self.assertNotEqual(None, wt.get_root_id())
+
+ def test_update_sets_updated_root_id(self):
+ wt = self.make_branch_and_tree('tree')
+ wt.set_root_id('first_root_id')
+ self.assertEqual('first_root_id', wt.get_root_id())
+ self.build_tree(['tree/file'])
+ wt.add(['file'])
+ wt.commit('first')
+ co = wt.branch.create_checkout('checkout')
+ wt.set_root_id('second_root_id')
+ wt.commit('second')
+ self.assertEqual('second_root_id', wt.get_root_id())
+ self.assertEqual(0, co.update())
+ self.assertEqual('second_root_id', co.get_root_id())
+
+ def test_update_returns_conflict_count(self):
+ # working tree formats from the meta-dir format and newer support
+ # setting the last revision on a tree independently of that on the
+ # branch. Its concievable that some future formats may want to
+ # couple them again (i.e. because its really a smart server and
+ # the working tree will always match the branch). So we test
+ # that formats where initialising a branch does not initialise a
+ # tree - and thus have separable entities - support skewing the
+ # two things.
+ main_branch = self.make_branch('tree')
+ try:
+ # if there is a working tree now, this is not supported.
+ main_branch.bzrdir.open_workingtree()
+ return
+ except errors.NoWorkingTree:
+ pass
+ wt = main_branch.bzrdir.create_workingtree()
+ # create an out of date working tree by making a checkout in this
+ # current format
+ self.build_tree(['checkout/', 'tree/file'])
+ checkout = bzrdir.BzrDirMetaFormat1().initialize('checkout')
+ checkout.set_branch_reference(main_branch)
+ old_tree = self.workingtree_format.initialize(checkout)
+ # now commit to 'tree'
+ wt.add('file')
+ wt.commit('A', rev_id='A')
+ # and add a file file to the checkout
+ self.build_tree(['checkout/file'])
+ old_tree.add('file')
+ # and update old_tree
+ self.assertEqual(1, old_tree.update())
+ self.assertEqual(['A'], old_tree.get_parent_ids())
+
+ def test_merge_revert(self):
+ from bzrlib.merge import merge_inner
+ this = self.make_branch_and_tree('b1')
+ self.build_tree_contents([('b1/a', 'a test\n'), ('b1/b', 'b test\n')])
+ this.add(['a', 'b'])
+ this.commit(message='')
+ base = this.bzrdir.clone('b2').open_workingtree()
+ self.build_tree_contents([('b2/a', 'b test\n')])
+ other = this.bzrdir.clone('b3').open_workingtree()
+ self.build_tree_contents([('b3/a', 'c test\n'), ('b3/c', 'c test\n')])
+ other.add('c')
+
+ self.build_tree_contents([('b1/b', 'q test\n'), ('b1/d', 'd test\n')])
+ # Note: If we don't lock this before calling merge_inner, then we get a
+ # lock-contention failure. This probably indicates something
+ # weird going on inside merge_inner. Probably something about
+ # calling bt = this_tree.basis_tree() in one lock, and then
+ # locking both this_tree and bt separately, causing a dirstate
+ # locking race.
+ this.lock_write()
+ self.addCleanup(this.unlock)
+ merge_inner(this.branch, other, base, this_tree=this)
+ a = open('b1/a', 'rb')
+ try:
+ self.assertNotEqual(a.read(), 'a test\n')
+ finally:
+ a.close()
+ this.revert()
+ self.assertFileEqual('a test\n', 'b1/a')
+ self.assertPathExists('b1/b.~1~')
+ self.assertPathDoesNotExist('b1/c')
+ self.assertPathDoesNotExist('b1/a.~1~')
+ self.assertPathExists('b1/d')
+
+ def test_update_updates_bound_branch_no_local_commits(self):
+ # doing an update in a tree updates the branch its bound to too.
+ master_tree = self.make_branch_and_tree('master')
+ tree = self.make_branch_and_tree('tree')
+ try:
+ tree.branch.bind(master_tree.branch)
+ except errors.UpgradeRequired:
+ # legacy branches cannot bind
+ return
+ master_tree.commit('foo', rev_id='foo', allow_pointless=True)
+ tree.update()
+ self.assertEqual(['foo'], tree.get_parent_ids())
+ self.assertEqual('foo', tree.branch.last_revision())
+
+ def test_update_turns_local_commit_into_merge(self):
+ # doing an update with a few local commits and no master commits
+ # makes pending-merges.
+ # this is done so that 'bzr update; bzr revert' will always produce
+ # an exact copy of the 'logical branch' - the referenced branch for
+ # a checkout, and the master for a bound branch.
+ # its possible that we should instead have 'bzr update' when there
+ # is nothing new on the master leave the current commits intact and
+ # alter 'revert' to revert to the master always. But for now, its
+ # good.
+ master_tree = self.make_branch_and_tree('master')
+ master_tip = master_tree.commit('first master commit')
+ tree = self.make_branch_and_tree('tree')
+ try:
+ tree.branch.bind(master_tree.branch)
+ except errors.UpgradeRequired:
+ # legacy branches cannot bind
+ return
+ # sync with master
+ tree.update()
+ # work locally
+ tree.commit('foo', rev_id='foo', allow_pointless=True, local=True)
+ tree.commit('bar', rev_id='bar', allow_pointless=True, local=True)
+ # sync with master prepatory to committing
+ tree.update()
+ # which should have pivoted the local tip into a merge
+ self.assertEqual([master_tip, 'bar'], tree.get_parent_ids())
+ # and the local branch history should match the masters now.
+ self.assertEqual(master_tree.branch.last_revision(),
+ tree.branch.last_revision())
+
+ def test_update_takes_revision_parameter(self):
+ wt = self.make_branch_and_tree('wt')
+ self.build_tree_contents([('wt/a', 'old content')])
+ wt.add(['a'])
+ rev1 = wt.commit('first master commit')
+ self.build_tree_contents([('wt/a', 'new content')])
+ rev2 = wt.commit('second master commit')
+ # https://bugs.launchpad.net/bzr/+bug/45719/comments/20
+ # when adding 'update -r' we should make sure all wt formats support
+ # it
+ conflicts = wt.update(revision=rev1)
+ self.assertFileEqual('old content', 'wt/a')
+ self.assertEqual([rev1], wt.get_parent_ids())
+
+ def test_merge_modified_detects_corruption(self):
+ # FIXME: This doesn't really test that it works; also this is not
+ # implementation-independent. mbp 20070226
+ tree = self.make_branch_and_tree('master')
+ if not isinstance(tree, InventoryWorkingTree):
+ raise TestNotApplicable("merge-hashes is specific to bzr "
+ "working trees")
+ tree._transport.put_bytes('merge-hashes', 'asdfasdf')
+ self.assertRaises(errors.MergeModifiedFormatError, tree.merge_modified)
+
+ def test_merge_modified(self):
+ # merge_modified stores a map from file id to hash
+ tree = self.make_branch_and_tree('tree')
+ d = {'file-id': osutils.sha_string('hello')}
+ self.build_tree_contents([('tree/somefile', 'hello')])
+ tree.lock_write()
+ try:
+ tree.add(['somefile'], ['file-id'])
+ tree.set_merge_modified(d)
+ mm = tree.merge_modified()
+ self.assertEquals(mm, d)
+ finally:
+ tree.unlock()
+ mm = tree.merge_modified()
+ self.assertEquals(mm, d)
+
+ def test_conflicts(self):
+ from bzrlib.tests.test_conflicts import example_conflicts
+ tree = self.make_branch_and_tree('master')
+ try:
+ tree.set_conflicts(example_conflicts)
+ except UnsupportedOperation:
+ raise TestSkipped('set_conflicts not supported')
+
+ tree2 = WorkingTree.open('master')
+ self.assertEqual(tree2.conflicts(), example_conflicts)
+ tree2._transport.put_bytes('conflicts', '')
+ self.assertRaises(errors.ConflictFormatError,
+ tree2.conflicts)
+ tree2._transport.put_bytes('conflicts', 'a')
+ self.assertRaises(errors.ConflictFormatError,
+ tree2.conflicts)
+
+ def make_merge_conflicts(self):
+ from bzrlib.merge import merge_inner
+ tree = self.make_branch_and_tree('mine')
+ with file('mine/bloo', 'wb') as f: f.write('one')
+ with file('mine/blo', 'wb') as f: f.write('on')
+ tree.add(['bloo', 'blo'])
+ tree.commit("blah", allow_pointless=False)
+ base = tree.branch.repository.revision_tree(tree.last_revision())
+ controldir.ControlDir.open("mine").sprout("other")
+ with file('other/bloo', 'wb') as f: f.write('two')
+ othertree = WorkingTree.open('other')
+ othertree.commit('blah', allow_pointless=False)
+ with file('mine/bloo', 'wb') as f: f.write('three')
+ tree.commit("blah", allow_pointless=False)
+ merge_inner(tree.branch, othertree, base, this_tree=tree)
+ return tree
+
+ def test_merge_conflicts(self):
+ tree = self.make_merge_conflicts()
+ self.assertEqual(len(tree.conflicts()), 1)
+
+ def test_clear_merge_conflicts(self):
+ tree = self.make_merge_conflicts()
+ self.assertEqual(len(tree.conflicts()), 1)
+ try:
+ tree.set_conflicts(ConflictList())
+ except UnsupportedOperation:
+ raise TestSkipped('unsupported operation')
+ self.assertEqual(tree.conflicts(), ConflictList())
+
+ def test_add_conflicts(self):
+ tree = self.make_branch_and_tree('tree')
+ try:
+ tree.add_conflicts([TextConflict('path_a')])
+ except UnsupportedOperation:
+ raise TestSkipped('unsupported operation')
+ self.assertEqual(ConflictList([TextConflict('path_a')]),
+ tree.conflicts())
+ tree.add_conflicts([TextConflict('path_a')])
+ self.assertEqual(ConflictList([TextConflict('path_a')]),
+ tree.conflicts())
+ tree.add_conflicts([ContentsConflict('path_a')])
+ self.assertEqual(ConflictList([ContentsConflict('path_a'),
+ TextConflict('path_a')]),
+ tree.conflicts())
+ tree.add_conflicts([TextConflict('path_b')])
+ self.assertEqual(ConflictList([ContentsConflict('path_a'),
+ TextConflict('path_a'),
+ TextConflict('path_b')]),
+ tree.conflicts())
+
+ def test_revert_clear_conflicts(self):
+ tree = self.make_merge_conflicts()
+ self.assertEqual(len(tree.conflicts()), 1)
+ tree.revert(["blo"])
+ self.assertEqual(len(tree.conflicts()), 1)
+ tree.revert(["bloo"])
+ self.assertEqual(len(tree.conflicts()), 0)
+
+ def test_revert_clear_conflicts2(self):
+ tree = self.make_merge_conflicts()
+ self.assertEqual(len(tree.conflicts()), 1)
+ tree.revert()
+ self.assertEqual(len(tree.conflicts()), 0)
+
+ def test_format_description(self):
+ tree = self.make_branch_and_tree('tree')
+ text = tree._format.get_format_description()
+ self.assertTrue(len(text))
+
+ def test_branch_attribute_is_not_settable(self):
+ # the branch attribute is an aspect of the working tree, not a
+ # configurable attribute
+ tree = self.make_branch_and_tree('tree')
+ def set_branch():
+ tree.branch = tree.branch
+ self.assertRaises(AttributeError, set_branch)
+
+ def test_list_files_versioned_before_ignored(self):
+ """A versioned file matching an ignore rule should not be ignored."""
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['foo.pyc'])
+ # ensure that foo.pyc is ignored
+ self.build_tree_contents([('.bzrignore', 'foo.pyc')])
+ tree.add('foo.pyc', 'anid')
+ tree.lock_read()
+ files = sorted(list(tree.list_files()))
+ tree.unlock()
+ self.assertEqual((u'.bzrignore', '?', 'file', None), files[0][:-1])
+ self.assertEqual((u'foo.pyc', 'V', 'file', 'anid'), files[1][:-1])
+ self.assertEqual(2, len(files))
+
+ def test_non_normalized_add_accessible(self):
+ try:
+ self.build_tree([u'a\u030a'])
+ except UnicodeError:
+ raise TestSkipped('Filesystem does not support unicode filenames')
+ tree = self.make_branch_and_tree('.')
+ orig = osutils.normalized_filename
+ osutils.normalized_filename = osutils._accessible_normalized_filename
+ try:
+ tree.add([u'a\u030a'])
+ tree.lock_read()
+ self.assertEqual([('', 'directory'), (u'\xe5', 'file')],
+ [(path, ie.kind) for path,ie in
+ tree.iter_entries_by_dir()])
+ tree.unlock()
+ finally:
+ osutils.normalized_filename = orig
+
+ def test_non_normalized_add_inaccessible(self):
+ try:
+ self.build_tree([u'a\u030a'])
+ except UnicodeError:
+ raise TestSkipped('Filesystem does not support unicode filenames')
+ tree = self.make_branch_and_tree('.')
+ orig = osutils.normalized_filename
+ osutils.normalized_filename = osutils._inaccessible_normalized_filename
+ try:
+ self.assertRaises(errors.InvalidNormalization,
+ tree.add, [u'a\u030a'])
+ finally:
+ osutils.normalized_filename = orig
+
+ def test__write_inventory(self):
+ # The private interface _write_inventory is currently used by transform.
+ tree = self.make_branch_and_tree('.')
+ if not isinstance(tree, InventoryWorkingTree):
+ raise TestNotApplicable("_write_inventory does not exist on "
+ "non-inventory working trees")
+ # if we write write an inventory then do a walkdirs we should get back
+ # missing entries, and actual, and unknowns as appropriate.
+ self.build_tree(['present', 'unknown'])
+ inventory = Inventory(tree.get_root_id())
+ inventory.add_path('missing', 'file', 'missing-id')
+ inventory.add_path('present', 'file', 'present-id')
+ # there is no point in being able to write an inventory to an unlocked
+ # tree object - its a low level api not a convenience api.
+ tree.lock_write()
+ tree._write_inventory(inventory)
+ tree.unlock()
+ tree.lock_read()
+ try:
+ present_stat = os.lstat('present')
+ unknown_stat = os.lstat('unknown')
+ expected_results = [
+ (('', tree.get_root_id()),
+ [('missing', 'missing', 'unknown', None, 'missing-id', 'file'),
+ ('present', 'present', 'file', present_stat, 'present-id', 'file'),
+ ('unknown', 'unknown', 'file', unknown_stat, None, None),
+ ]
+ )]
+ self.assertEqual(expected_results, list(tree.walkdirs()))
+ finally:
+ tree.unlock()
+
+ def test_path2id(self):
+ # smoke test for path2id
+ tree = self.make_branch_and_tree('.')
+ self.build_tree(['foo'])
+ tree.add(['foo'], ['foo-id'])
+ self.assertEqual('foo-id', tree.path2id('foo'))
+ # the next assertion is for backwards compatability with WorkingTree3,
+ # though its probably a bad idea, it makes things work. Perhaps
+ # it should raise a deprecation warning?
+ self.assertEqual('foo-id', tree.path2id('foo/'))
+
+ def test_filter_unversioned_files(self):
+ # smoke test for filter_unversioned_files
+ tree = self.make_branch_and_tree('.')
+ paths = ['here-and-versioned', 'here-and-not-versioned',
+ 'not-here-and-versioned', 'not-here-and-not-versioned']
+ tree.add(['here-and-versioned', 'not-here-and-versioned'],
+ kinds=['file', 'file'])
+ self.build_tree(['here-and-versioned', 'here-and-not-versioned'])
+ tree.lock_read()
+ self.addCleanup(tree.unlock)
+ self.assertEqual(
+ set(['not-here-and-not-versioned', 'here-and-not-versioned']),
+ tree.filter_unversioned_files(paths))
+
+ def test_detect_real_kind(self):
+ # working trees report the real kind of the file on disk, not the kind
+ # they had when they were first added
+ # create one file of every interesting type
+ tree = self.make_branch_and_tree('.')
+ tree.lock_write()
+ self.addCleanup(tree.unlock)
+ self.build_tree(['file', 'directory/'])
+ names = ['file', 'directory']
+ if has_symlinks():
+ os.symlink('target', 'symlink')
+ names.append('symlink')
+ tree.add(names, [n + '-id' for n in names])
+ # now when we first look, we should see everything with the same kind
+ # with which they were initially added
+ for n in names:
+ actual_kind = tree.kind(n + '-id')
+ self.assertEqual(n, actual_kind)
+ # move them around so the names no longer correspond to the types
+ os.rename(names[0], 'tmp')
+ for i in range(1, len(names)):
+ os.rename(names[i], names[i-1])
+ os.rename('tmp', names[-1])
+ # now look and expect to see the correct types again
+ for i in range(len(names)):
+ actual_kind = tree.kind(names[i-1] + '-id')
+ expected_kind = names[i]
+ self.assertEqual(expected_kind, actual_kind)
+
+ def test_stored_kind_with_missing(self):
+ tree = self.make_branch_and_tree('tree')
+ tree.lock_write()
+ self.addCleanup(tree.unlock)
+ self.build_tree(['tree/a', 'tree/b/'])
+ tree.add(['a', 'b'], ['a-id', 'b-id'])
+ os.unlink('tree/a')
+ os.rmdir('tree/b')
+ self.assertEqual('file', tree.stored_kind('a-id'))
+ self.assertEqual('directory', tree.stored_kind('b-id'))
+
+ def test_missing_file_sha1(self):
+ """If a file is missing, its sha1 should be reported as None."""
+ tree = self.make_branch_and_tree('.')
+ tree.lock_write()
+ self.addCleanup(tree.unlock)
+ self.build_tree(['file'])
+ tree.add('file', 'file-id')
+ tree.commit('file added')
+ os.unlink('file')
+ self.assertIs(None, tree.get_file_sha1('file-id'))
+
+ def test_no_file_sha1(self):
+ """If a file is not present, get_file_sha1 should raise NoSuchId"""
+ tree = self.make_branch_and_tree('.')
+ tree.lock_write()
+ self.addCleanup(tree.unlock)
+ self.assertRaises(errors.NoSuchId, tree.get_file_sha1, 'file-id')
+ self.build_tree(['file'])
+ tree.add('file', 'file-id')
+ tree.commit('foo')
+ tree.remove('file')
+ self.assertRaises(errors.NoSuchId, tree.get_file_sha1, 'file-id')
+
+ def test_case_sensitive(self):
+ """If filesystem is case-sensitive, tree should report this.
+
+ We check case-sensitivity by creating a file with a lowercase name,
+ then testing whether it exists with an uppercase name.
+ """
+ self.build_tree(['filename'])
+ if os.path.exists('FILENAME'):
+ case_sensitive = False
+ else:
+ case_sensitive = True
+ tree = self.make_branch_and_tree('test')
+ self.assertEqual(case_sensitive, tree.case_sensitive)
+ if not isinstance(tree, InventoryWorkingTree):
+ raise TestNotApplicable("get_format_string is only available "
+ "on bzr working trees")
+ # now we cheat, and make a file that matches the case-sensitive name
+ t = tree.bzrdir.get_workingtree_transport(None)
+ try:
+ content = tree._format.get_format_string()
+ except NotImplementedError:
+ # All-in-one formats didn't have a separate format string.
+ content = tree.bzrdir._format.get_format_string()
+ t.put_bytes(tree._format.case_sensitive_filename, content)
+ tree = tree.bzrdir.open_workingtree()
+ self.assertFalse(tree.case_sensitive)
+
+ def test_supports_executable(self):
+ self.build_tree(['filename'])
+ tree = self.make_branch_and_tree('.')
+ tree.add('filename')
+ self.assertIsInstance(tree._supports_executable(), bool)
+ if tree._supports_executable():
+ tree.lock_read()
+ try:
+ self.assertFalse(tree.is_executable(tree.path2id('filename')))
+ finally:
+ tree.unlock()
+ os.chmod('filename', 0755)
+ self.addCleanup(tree.lock_read().unlock)
+ self.assertTrue(tree.is_executable(tree.path2id('filename')))
+ else:
+ self.addCleanup(tree.lock_read().unlock)
+ self.assertFalse(tree.is_executable(tree.path2id('filename')))
+
+ def test_all_file_ids_with_missing(self):
+ tree = self.make_branch_and_tree('tree')
+ tree.lock_write()
+ self.addCleanup(tree.unlock)
+ self.build_tree(['tree/a', 'tree/b'])
+ tree.add(['a', 'b'], ['a-id', 'b-id'])
+ os.unlink('tree/a')
+ self.assertEqual(set(['a-id', 'b-id', tree.get_root_id()]),
+ tree.all_file_ids())
+
+ def test_sprout_hardlink(self):
+ real_os_link = getattr(os, 'link', None)
+ if real_os_link is None:
+ raise TestNotApplicable("This platform doesn't provide os.link")
+ source = self.make_branch_and_tree('source')
+ self.build_tree(['source/file'])
+ source.add('file')
+ source.commit('added file')
+ def fake_link(source, target):
+ raise OSError(errno.EPERM, 'Operation not permitted')
+ os.link = fake_link
+ try:
+ # Hard-link support is optional, so supplying hardlink=True may
+ # or may not raise an exception. But if it does, it must be
+ # HardLinkNotSupported
+ try:
+ source.bzrdir.sprout('target', accelerator_tree=source,
+ hardlink=True)
+ except errors.HardLinkNotSupported:
+ pass
+ finally:
+ os.link = real_os_link
+
+
+class TestWorkingTreeUpdate(TestCaseWithWorkingTree):
+
+ def make_diverged_master_branch(self):
+ """
+ B: wt.branch.last_revision()
+ M: wt.branch.get_master_branch().last_revision()
+ W: wt.last_revision()
+
+
+ 1
+ |\
+ B-2 3
+ | |
+ 4 5-M
+ |
+ W
+ """
+ format = self.workingtree_format.get_controldir_for_branch()
+ builder = self.make_branch_builder(".", format=format)
+ builder.start_series()
+ # mainline
+ builder.build_snapshot(
+ '1', None,
+ [('add', ('', 'root-id', 'directory', '')),
+ ('add', ('file1', 'file1-id', 'file', 'file1 content\n'))])
+ # branch
+ builder.build_snapshot('2', ['1'], [])
+ builder.build_snapshot(
+ '4', ['2'],
+ [('add', ('file4', 'file4-id', 'file', 'file4 content\n'))])
+ # master
+ builder.build_snapshot('3', ['1'], [])
+ builder.build_snapshot(
+ '5', ['3'],
+ [('add', ('file5', 'file5-id', 'file', 'file5 content\n'))])
+ builder.finish_series()
+ return builder, builder._branch.last_revision()
+
+ def make_checkout_and_master(self, builder, wt_path, master_path, wt_revid,
+ master_revid=None, branch_revid=None):
+ """Build a lightweight checkout and its master branch."""
+ if master_revid is None:
+ master_revid = wt_revid
+ if branch_revid is None:
+ branch_revid = master_revid
+ final_branch = builder.get_branch()
+ # The master branch
+ master = final_branch.bzrdir.sprout(master_path,
+ master_revid).open_branch()
+ # The checkout
+ wt = self.make_branch_and_tree(wt_path)
+ wt.pull(final_branch, stop_revision=wt_revid)
+ wt.branch.pull(final_branch, stop_revision=branch_revid, overwrite=True)
+ try:
+ wt.branch.bind(master)
+ except errors.UpgradeRequired:
+ raise TestNotApplicable(
+ "Can't bind %s" % wt.branch._format.__class__)
+ return wt, master
+
+ def test_update_remove_commit(self):
+ """Update should remove revisions when the branch has removed
+ some commits.
+
+ We want to revert 4, so that strating with the
+ make_diverged_master_branch() graph the final result should be
+ equivalent to:
+
+ 1
+ |\
+ 3 2
+ | |\
+ MB-5 | 4
+ |/
+ W
+
+ And the changes in 4 have been removed from the WT.
+ """
+ builder, tip = self.make_diverged_master_branch()
+ wt, master = self.make_checkout_and_master(
+ builder, 'checkout', 'master', '4',
+ master_revid=tip, branch_revid='2')
+ # First update the branch
+ old_tip = wt.branch.update()
+ self.assertEqual('2', old_tip)
+ # No conflicts should occur
+ self.assertEqual(0, wt.update(old_tip=old_tip))
+ # We are in sync with the master
+ self.assertEqual(tip, wt.branch.last_revision())
+ # We have the right parents ready to be committed
+ self.assertEqual(['5', '2'], wt.get_parent_ids())
+
+ def test_update_revision(self):
+ builder, tip = self.make_diverged_master_branch()
+ wt, master = self.make_checkout_and_master(
+ builder, 'checkout', 'master', '4',
+ master_revid=tip, branch_revid='2')
+ self.assertEqual(0, wt.update(revision='1'))
+ self.assertEqual('1', wt.last_revision())
+ self.assertEqual(tip, wt.branch.last_revision())
+ self.assertPathExists('checkout/file1')
+ self.assertPathDoesNotExist('checkout/file4')
+ self.assertPathDoesNotExist('checkout/file5')
+
+
+class TestIllegalPaths(TestCaseWithWorkingTree):
+
+ def test_bad_fs_path(self):
+ if osutils.normalizes_filenames():
+ # You *can't* create an illegal filename on OSX.
+ raise tests.TestNotApplicable('OSX normalizes filenames')
+ self.requireFeature(features.UTF8Filesystem)
+ # We require a UTF8 filesystem, because otherwise we would need to get
+ # tricky to figure out how to create an illegal filename.
+ # \xb5 is an illegal path because it should be \xc2\xb5 for UTF-8
+ tree = self.make_branch_and_tree('tree')
+ self.build_tree(['tree/subdir/'])
+ tree.add('subdir')
+
+ f = open('tree/subdir/m\xb5', 'wb')
+ try:
+ f.write('trivial\n')
+ finally:
+ f.close()
+
+ tree.lock_read()
+ self.addCleanup(tree.unlock)
+ basis = tree.basis_tree()
+ basis.lock_read()
+ self.addCleanup(basis.unlock)
+
+ e = self.assertListRaises(errors.BadFilenameEncoding,
+ tree.iter_changes, tree.basis_tree(),
+ want_unversioned=True)
+ # We should display the relative path
+ self.assertEqual('subdir/m\xb5', e.filename)
+ self.assertEqual(osutils._fs_enc, e.fs_encoding)
+
+
+class TestControlComponent(TestCaseWithWorkingTree):
+ """WorkingTree implementations adequately implement ControlComponent."""
+
+ def test_urls(self):
+ wt = self.make_branch_and_tree('wt')
+ self.assertIsInstance(wt.user_url, str)
+ self.assertEqual(wt.user_url, wt.user_transport.base)
+ # for all current bzrdir implementations the user dir must be
+ # above the control dir but we might need to relax that?
+ self.assertEqual(wt.control_url.find(wt.user_url), 0)
+ self.assertEqual(wt.control_url, wt.control_transport.base)
+
+
+class TestWorthSavingLimit(TestCaseWithWorkingTree):
+
+ def make_wt_with_worth_saving_limit(self):
+ wt = self.make_branch_and_tree('wt')
+ if getattr(wt, '_worth_saving_limit', None) is None:
+ raise tests.TestNotApplicable('no _worth_saving_limit for'
+ ' this tree type')
+ wt.lock_write()
+ self.addCleanup(wt.unlock)
+ return wt
+
+ def test_not_set(self):
+ # Default should be 10
+ wt = self.make_wt_with_worth_saving_limit()
+ self.assertEqual(10, wt._worth_saving_limit())
+ ds = wt.current_dirstate()
+ self.assertEqual(10, ds._worth_saving_limit)
+
+ def test_set_in_branch(self):
+ wt = self.make_wt_with_worth_saving_limit()
+ conf = wt.get_config_stack()
+ conf.set('bzr.workingtree.worth_saving_limit', '20')
+ self.assertEqual(20, wt._worth_saving_limit())
+ ds = wt.current_dirstate()
+ self.assertEqual(10, ds._worth_saving_limit)
+
+ def test_invalid(self):
+ wt = self.make_wt_with_worth_saving_limit()
+ conf = wt.get_config_stack()
+ conf.set('bzr.workingtree.worth_saving_limit', 'a')
+ # If the config entry is invalid, default to 10
+ warnings = []
+ def warning(*args):
+ warnings.append(args[0] % args[1:])
+ self.overrideAttr(trace, 'warning', warning)
+ self.assertEqual(10, wt._worth_saving_limit())
+ self.assertLength(1, warnings)
+ self.assertEquals('Value "a" is not valid for'
+ ' "bzr.workingtree.worth_saving_limit"',
+ warnings[0])
+
+
+class TestFormatAttributes(TestCaseWithWorkingTree):
+
+ def test_versioned_directories(self):
+ self.assertSubset(
+ [self.workingtree_format.supports_versioned_directories],
+ (True, False))