summaryrefslogtreecommitdiff
path: root/bzrlib/tests/per_intertree/test_compare.py
diff options
context:
space:
mode:
Diffstat (limited to 'bzrlib/tests/per_intertree/test_compare.py')
-rw-r--r--bzrlib/tests/per_intertree/test_compare.py1846
1 files changed, 1846 insertions, 0 deletions
diff --git a/bzrlib/tests/per_intertree/test_compare.py b/bzrlib/tests/per_intertree/test_compare.py
new file mode 100644
index 0000000..76bc131
--- /dev/null
+++ b/bzrlib/tests/per_intertree/test_compare.py
@@ -0,0 +1,1846 @@
+# Copyright (C) 2006, 2007 Canonical Ltd
+#
+# 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
+
+"""Tests for the InterTree.compare() function."""
+
+import os
+import shutil
+
+from bzrlib import (
+ errors,
+ mutabletree,
+ tests,
+ )
+from bzrlib.osutils import has_symlinks
+from bzrlib.tests.per_intertree import TestCaseWithTwoTrees
+from bzrlib.tests import (
+ features,
+ )
+
+# TODO: test the include_root option.
+# TODO: test that renaming a directory x->y does not emit a rename for the
+# child x/a->y/a.
+# TODO: test that renaming a directory x-> does not emit a rename for the child
+# x/a -> y/a when a supplied_files argument gives either 'x/' or 'y/a'
+# -> that is, when the renamed parent is not processed by the function.
+# TODO: test items are only emitted once when a specific_files list names a dir
+# whose parent is now a child.
+# TODO: test comparisons between trees with different root ids. mbp 20070301
+#
+# TODO: More comparisons between trees with subtrees in different states.
+#
+# TODO: Many tests start out by setting the tree roots ids the same, maybe
+# that should just be the default for these tests, by changing
+# make_branch_and_tree. mbp 20070307
+
+class TestCompare(TestCaseWithTwoTrees):
+
+ def test_compare_empty_trees(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_no_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare()
+ self.assertEqual([], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_empty_to_abc_content(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare()
+ self.assertEqual([('a', 'a-id', 'file'),
+ ('b', 'b-id', 'directory'),
+ ('b/c', 'c-id', 'file'),
+ ], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_dangling(self):
+ # This test depends on the ability for some trees to have a difference
+ # between a 'versioned present' and 'versioned not present' (aka
+ # dangling) file. In this test there are two trees each with a separate
+ # dangling file, and the dangling files should be considered absent for
+ # the test.
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['2/a'])
+ tree2.add('a')
+ os.unlink('2/a')
+ self.build_tree(['1/b'])
+ tree1.add('b')
+ os.unlink('1/b')
+ # the conversion to test trees here will leave the trees intact for the
+ # default intertree, but may perform a commit for other tree types,
+ # which may reduce the validity of the test. XXX: Think about how to
+ # address this.
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare()
+ self.assertEqual([], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_abc_content_to_empty(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_no_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare()
+ self.assertEqual([], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([('a', 'a-id', 'file'),
+ ('b', 'b-id', 'directory'),
+ ('b/c', 'c-id', 'file'),
+ ], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_content_modification(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_2(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare()
+ self.assertEqual([], d.added)
+ self.assertEqual([('a', 'a-id', 'file', True, False)], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_meta_modification(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_3(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare()
+ self.assertEqual([], d.added)
+ self.assertEqual([('b/c', 'c-id', 'file', False, True)], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_file_rename(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_4(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare()
+ self.assertEqual([], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([('a', 'd', 'a-id', 'file', False, False)], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_file_rename_and_modification(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_5(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare()
+ self.assertEqual([], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([('a', 'd', 'a-id', 'file', True, False)], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_file_rename_and_meta_modification(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_6(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare()
+ self.assertEqual([], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([('b/c', 'e', 'c-id', 'file', False, True)], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_empty_to_abc_content_a_only(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare(specific_files=['a'])
+ self.assertEqual([('a', 'a-id', 'file')], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_empty_to_abc_content_a_and_c_only(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare(
+ specific_files=['a', 'b/c'])
+ self.assertEqual(
+ [('a', 'a-id', 'file'), (u'b', 'b-id', 'directory'),
+ ('b/c', 'c-id', 'file')],
+ d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_empty_to_abc_content_c_only(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare(
+ specific_files=['b/c'])
+ self.assertEqual(
+ [(u'b', 'b-id', 'directory'), ('b/c', 'c-id', 'file')], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_empty_to_abc_content_b_only(self):
+ """Restricting to a dir matches the children of the dir."""
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare(specific_files=['b'])
+ self.assertEqual(
+ [('b', 'b-id', 'directory'), ('b/c', 'c-id', 'file')],
+ d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_unchanged_with_renames_and_modifications(self):
+ """want_unchanged should generate a list of unchanged entries."""
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_5(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare(want_unchanged=True)
+ self.assertEqual([], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([('a', 'd', 'a-id', 'file', True, False)], d.renamed)
+ self.assertEqual(
+ [(u'b', 'b-id', 'directory'), (u'b/c', 'c-id', 'file')],
+ d.unchanged)
+
+ def test_extra_trees_finds_ids(self):
+ """Ask for a delta between two trees with a path present in a third."""
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_3(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare(specific_files=['b'])
+ # the type of tree-3 does not matter - it is used as a lookup, not
+ # a dispatch. XXX: For dirstate it does speak to the optimisability of
+ # the lookup, in merged trees it can be fast-pathed. We probably want
+ # two tests: one as is, and one with it as a pending merge.
+ tree3 = self.make_branch_and_tree('3')
+ tree3 = self.get_tree_no_parents_abc_content_6(tree3)
+ tree3.lock_read()
+ self.addCleanup(tree3.unlock)
+ # tree 3 has 'e' which is 'c-id'. Tree 1 has c-id at b/c, and Tree 2
+ # has c-id at b/c with its exec flag toggled.
+ # without extra_trees, we should get no modifications from this
+ # so do one, to be sure the test is valid.
+ d = self.intertree_class(tree1, tree2).compare(
+ specific_files=['e'])
+ self.assertEqual([], d.modified)
+ # now give it an additional lookup:
+ d = self.intertree_class(tree1, tree2).compare(
+ specific_files=['e'], extra_trees=[tree3])
+ self.assertEqual([], d.added)
+ self.assertEqual([('b/c', 'c-id', 'file', False, True)], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+
+ def test_require_versioned(self):
+ # this does not quite robustly test, as it is passing in missing paths
+ # rather than present-but-not-versioned paths. At the moment there is
+ # no mechanism for managing the test trees (which are readonly) to
+ # get present-but-not-versioned files for trees that can do that.
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ self.assertRaises(errors.PathsNotVersionedError,
+ self.intertree_class(tree1, tree2).compare,
+ specific_files=['d'],
+ require_versioned=True)
+
+ def test_default_ignores_unversioned_files(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree1/a', 'tree1/c',
+ 'tree2/a', 'tree2/b', 'tree2/c'])
+ tree1.add(['a', 'c'], ['a-id', 'c-id'])
+ tree2.add(['a', 'c'], ['a-id', 'c-id'])
+
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ d = self.intertree_class(tree1, tree2).compare()
+ self.assertEqual([], d.added)
+ self.assertEqual([(u'a', 'a-id', 'file', True, False),
+ (u'c', 'c-id', 'file', True, False)], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+ self.assertEqual([], d.unversioned)
+
+ def test_unversioned_paths_in_tree(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree2/file', 'tree2/dir/'])
+ if has_symlinks():
+ os.symlink('target', 'tree2/link')
+ links_supported = True
+ else:
+ links_supported = False
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ self.not_applicable_if_cannot_represent_unversioned(tree2)
+ d = self.intertree_class(tree1, tree2).compare(want_unversioned=True)
+ self.assertEqual([], d.added)
+ self.assertEqual([], d.modified)
+ self.assertEqual([], d.removed)
+ self.assertEqual([], d.renamed)
+ self.assertEqual([], d.unchanged)
+ expected_unversioned = [(u'dir', None, 'directory'),
+ (u'file', None, 'file')]
+ if links_supported:
+ expected_unversioned.append((u'link', None, 'symlink'))
+ self.assertEqual(expected_unversioned, d.unversioned)
+
+
+class TestIterChanges(TestCaseWithTwoTrees):
+ """Test the comparison iterator"""
+
+ def assertEqualIterChanges(self, left_changes, right_changes):
+ """Assert that left_changes == right_changes.
+
+ :param left_changes: A list of the output from iter_changes.
+ :param right_changes: A list of the output from iter_changes.
+ """
+ left_changes = sorted(left_changes)
+ right_changes = sorted(right_changes)
+ if left_changes == right_changes:
+ return
+ # setify to get item by item differences, but we can only do this
+ # when all the ids are unique on both sides.
+ left_dict = dict((item[0], item) for item in left_changes)
+ right_dict = dict((item[0], item) for item in right_changes)
+ if (len(left_dict) != len(left_changes) or
+ len(right_dict) != len(right_changes)):
+ # Can't do a direct comparison. We could do a sequence diff, but
+ # for now just do a regular assertEqual for now.
+ self.assertEqual(left_changes, right_changes)
+ keys = set(left_dict).union(set(right_dict))
+ different = []
+ same = []
+ for key in keys:
+ left_item = left_dict.get(key)
+ right_item = right_dict.get(key)
+ if left_item == right_item:
+ same.append(str(left_item))
+ else:
+ different.append(" %s\n %s" % (left_item, right_item))
+ self.fail("iter_changes output different. Unchanged items:\n" +
+ "\n".join(same) + "\nChanged items:\n" + "\n".join(different))
+
+ def do_iter_changes(self, tree1, tree2, **extra_args):
+ """Helper to run iter_changes from tree1 to tree2.
+
+ :param tree1, tree2: The source and target trees. These will be locked
+ automatically.
+ :param **extra_args: Extra args to pass to iter_changes. This is not
+ inspected by this test helper.
+ """
+ tree1.lock_read()
+ tree2.lock_read()
+ try:
+ # sort order of output is not strictly defined
+ return sorted(self.intertree_class(tree1, tree2)
+ .iter_changes(**extra_args))
+ finally:
+ tree1.unlock()
+ tree2.unlock()
+
+ def check_has_changes(self, expected, tree1, tree2):
+ # has_changes is defined for mutable trees only
+ if not isinstance(tree2, mutabletree.MutableTree):
+ if isinstance(tree1, mutabletree.MutableTree):
+ # Let's switch the trees since has_changes() is commutative
+ # (where we can apply it)
+ tree2, tree1 = tree1, tree2
+ else:
+ # Neither tree can be used
+ return
+ tree1.lock_read()
+ try:
+ tree2.lock_read()
+ try:
+ return tree2.has_changes(tree1)
+ finally:
+ tree2.unlock()
+ finally:
+ tree1.unlock()
+
+ def mutable_trees_to_locked_test_trees(self, tree1, tree2):
+ """Convert the working trees into test trees.
+
+ Read lock them, and add the unlock to the cleanup.
+ """
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ tree1.lock_read()
+ self.addCleanup(tree1.unlock)
+ tree2.lock_read()
+ self.addCleanup(tree2.unlock)
+ return tree1, tree2
+
+ def make_tree_with_special_names(self):
+ """Create a tree with filenames chosen to exercise the walk order."""
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ paths, path_ids = self._create_special_names(tree2, 'tree2')
+ tree2.commit('initial', rev_id='rev-1')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ return (tree1, tree2, paths, path_ids)
+
+ def make_trees_with_special_names(self):
+ """Both trees will use the special names.
+
+ But the contents will differ for each file.
+ """
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ paths, path_ids = self._create_special_names(tree1, 'tree1')
+ paths, path_ids = self._create_special_names(tree2, 'tree2')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ return (tree1, tree2, paths, path_ids)
+
+ def _create_special_names(self, tree, base_path):
+ """Create a tree with paths that expose differences in sort orders."""
+ # Each directory will have a single file named 'f' inside
+ dirs = ['a',
+ 'a-a',
+ 'a/a',
+ 'a/a-a',
+ 'a/a/a',
+ 'a/a/a-a',
+ 'a/a/a/a',
+ 'a/a/a/a-a',
+ 'a/a/a/a/a',
+ ]
+ with_slashes = []
+ paths = []
+ path_ids = []
+ for d in dirs:
+ with_slashes.append(base_path + '/' + d + '/')
+ with_slashes.append(base_path + '/' + d + '/f')
+ paths.append(d)
+ paths.append(d+'/f')
+ path_ids.append(d.replace('/', '_') + '-id')
+ path_ids.append(d.replace('/', '_') + '_f-id')
+ self.build_tree(with_slashes)
+ tree.add(paths, path_ids)
+ return paths, path_ids
+
+ def test_compare_empty_trees(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_no_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ self.assertEqual([], self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(False, tree1, tree2)
+
+ def added(self, tree, file_id):
+ path, entry = self.get_path_entry(tree, file_id)
+ return (file_id, (None, path), True, (False, True), (None, entry.parent_id),
+ (None, entry.name), (None, entry.kind),
+ (None, entry.executable))
+
+ @staticmethod
+ def get_path_entry(tree, file_id):
+ iterator = tree.iter_entries_by_dir(specific_file_ids=[file_id])
+ return iterator.next()
+
+ def content_changed(self, tree, file_id):
+ path, entry = self.get_path_entry(tree, file_id)
+ return (file_id, (path, path), True, (True, True),
+ (entry.parent_id, entry.parent_id),
+ (entry.name, entry.name), (entry.kind, entry.kind),
+ (entry.executable, entry.executable))
+
+ def kind_changed(self, from_tree, to_tree, file_id):
+ from_path, old_entry = self.get_path_entry(from_tree, file_id)
+ path, new_entry = self.get_path_entry(to_tree, file_id)
+ return (file_id, (from_path, path), True, (True, True),
+ (old_entry.parent_id, new_entry.parent_id),
+ (old_entry.name, new_entry.name),
+ (old_entry.kind, new_entry.kind),
+ (old_entry.executable, new_entry.executable))
+
+ def missing(self, file_id, from_path, to_path, parent_id, kind):
+ _, from_basename = os.path.split(from_path)
+ _, to_basename = os.path.split(to_path)
+ # missing files have both paths, but no kind.
+ return (file_id, (from_path, to_path), True, (True, True),
+ (parent_id, parent_id),
+ (from_basename, to_basename), (kind, None), (False, False))
+
+ def deleted(self, tree, file_id):
+ entry = tree.root_inventory[file_id]
+ path = tree.id2path(file_id)
+ return (file_id, (path, None), True, (True, False), (entry.parent_id, None),
+ (entry.name, None), (entry.kind, None),
+ (entry.executable, None))
+
+ def renamed(self, from_tree, to_tree, file_id, content_changed):
+ from_path, from_entry = self.get_path_entry(from_tree, file_id)
+ to_path, to_entry = self.get_path_entry(to_tree, file_id)
+ return (file_id, (from_path, to_path), content_changed, (True, True),
+ (from_entry.parent_id, to_entry.parent_id),
+ (from_entry.name, to_entry.name),
+ (from_entry.kind, to_entry.kind),
+ (from_entry.executable, to_entry.executable))
+
+ def unchanged(self, tree, file_id):
+ path, entry = self.get_path_entry(tree, file_id)
+ parent = entry.parent_id
+ name = entry.name
+ kind = entry.kind
+ executable = entry.executable
+ return (file_id, (path, path), False, (True, True),
+ (parent, parent), (name, name), (kind, kind),
+ (executable, executable))
+
+ def unversioned(self, tree, path):
+ """Create an unversioned result."""
+ _, basename = os.path.split(path)
+ kind = tree._comparison_data(None, path)[0]
+ return (None, (None, path), True, (False, False), (None, None),
+ (None, basename), (None, kind),
+ (None, False))
+
+ def test_empty_to_abc_content(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ expected_results = sorted([
+ self.added(tree2, 'root-id'),
+ self.added(tree2, 'a-id'),
+ self.added(tree2, 'b-id'),
+ self.added(tree2, 'c-id'),
+ self.deleted(tree1, 'empty-root-id')])
+ self.assertEqual(expected_results, self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_empty_specific_files(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.assertEqual([],
+ self.do_iter_changes(tree1, tree2, specific_files=[]))
+
+ def test_no_specific_files(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ expected_results = sorted([
+ self.added(tree2, 'root-id'),
+ self.added(tree2, 'a-id'),
+ self.added(tree2, 'b-id'),
+ self.added(tree2, 'c-id'),
+ self.deleted(tree1, 'empty-root-id')])
+ self.assertEqual(expected_results, self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_empty_to_abc_content_a_only(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.assertEqual(
+ sorted([self.added(tree2, 'root-id'),
+ self.added(tree2, 'a-id'),
+ self.deleted(tree1, 'empty-root-id')]),
+ self.do_iter_changes(tree1, tree2, specific_files=['a']))
+
+ def test_abc_content_to_empty_a_only(self):
+ # For deletes we don't need to pickup parents.
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_no_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.assertEqual(
+ [self.deleted(tree1, 'a-id')],
+ self.do_iter_changes(tree1, tree2, specific_files=['a']))
+
+ def test_abc_content_to_empty_b_only(self):
+ # When b stops being a directory we have to pick up b/c as well.
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_no_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.assertEqual(
+ [self.deleted(tree1, 'b-id'), self.deleted(tree1, 'c-id')],
+ self.do_iter_changes(tree1, tree2, specific_files=['b']))
+
+ def test_empty_to_abc_content_a_and_c_only(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_no_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ expected_result = sorted([self.added(tree2, 'root-id'),
+ self.added(tree2, 'a-id'), self.added(tree2, 'b-id'),
+ self.added(tree2, 'c-id'), self.deleted(tree1, 'empty-root-id')])
+ self.assertEqual(expected_result,
+ self.do_iter_changes(tree1, tree2, specific_files=['a', 'b/c']))
+
+ def test_abc_content_to_empty(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_no_content(tree2)
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ expected_results = sorted([
+ self.added(tree2, 'empty-root-id'),
+ self.deleted(tree1, 'root-id'), self.deleted(tree1, 'a-id'),
+ self.deleted(tree1, 'b-id'), self.deleted(tree1, 'c-id')])
+ self.assertEqual(
+ expected_results,
+ self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_content_modification(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_2(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ root_id = tree1.path2id('')
+ self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
+ (root_id, root_id), ('a', 'a'),
+ ('file', 'file'), (False, False))],
+ self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_meta_modification(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_3(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ self.assertEqual([('c-id', ('b/c', 'b/c'), False, (True, True),
+ ('b-id', 'b-id'), ('c', 'c'), ('file', 'file'),
+ (False, True))],
+ self.do_iter_changes(tree1, tree2))
+
+ def test_empty_dir(self):
+ """an empty dir should not cause glitches to surrounding files."""
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ # the pathname is chosen to fall between 'a' and 'b'.
+ self.build_tree(['1/a-empty/', '2/a-empty/'])
+ tree1.add(['a-empty'], ['a-empty'])
+ tree2.add(['a-empty'], ['a-empty'])
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ expected = []
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
+
+ def test_file_rename(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_4(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ root_id = tree1.path2id('')
+ self.assertEqual([('a-id', ('a', 'd'), False, (True, True),
+ (root_id, root_id), ('a', 'd'), ('file', 'file'),
+ (False, False))],
+ self.do_iter_changes(tree1, tree2))
+
+ def test_file_rename_and_modification(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_5(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ root_id = tree1.path2id('')
+ self.assertEqual([('a-id', ('a', 'd'), True, (True, True),
+ (root_id, root_id), ('a', 'd'), ('file', 'file'),
+ (False, False))],
+ self.do_iter_changes(tree1, tree2))
+
+ def test_specific_content_modification_grabs_parents(self):
+ # WHen the only direct change to a specified file is a content change,
+ # and its in a reparented subtree, the parents are grabbed.
+ tree1 = self.make_branch_and_tree('1')
+ tree1.mkdir('changing', 'parent-id')
+ tree1.mkdir('changing/unchanging', 'mid-id')
+ tree1.add(['changing/unchanging/file'], ['file-id'], ['file'])
+ tree1.put_file_bytes_non_atomic('file-id', 'a file')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree2.mkdir('changed', 'parent-id')
+ tree2.mkdir('changed/unchanging', 'mid-id')
+ tree2.add(['changed/unchanging/file'], ['file-id'], ['file'])
+ tree2.put_file_bytes_non_atomic('file-id', 'changed content')
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ # parent-id has changed, as has file-id
+ root_id = tree1.path2id('')
+ self.assertEqualIterChanges(
+ [self.renamed(tree1, tree2, 'parent-id', False),
+ self.renamed(tree1, tree2, 'file-id', True)],
+ self.do_iter_changes(tree1, tree2,
+ specific_files=['changed/unchanging/file']))
+
+ def test_specific_content_modification_grabs_parents_root_changes(self):
+ # WHen the only direct change to a specified file is a content change,
+ # and its in a reparented subtree, the parents are grabbed, even if
+ # that includes the root.
+ tree1 = self.make_branch_and_tree('1')
+ tree1.set_root_id('old')
+ tree1.mkdir('changed', 'parent-id')
+ tree1.mkdir('changed/unchanging', 'mid-id')
+ tree1.add(['changed/unchanging/file'], ['file-id'], ['file'])
+ tree1.put_file_bytes_non_atomic('file-id', 'a file')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id('new')
+ tree2.mkdir('changed', 'parent-id')
+ tree2.mkdir('changed/unchanging', 'mid-id')
+ tree2.add(['changed/unchanging/file'], ['file-id'], ['file'])
+ tree2.put_file_bytes_non_atomic('file-id', 'changed content')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ # old is gone, new is added, parent-id has changed(reparented), as has
+ # file-id(content)
+ root_id = tree1.path2id('')
+ self.assertEqualIterChanges(
+ [self.renamed(tree1, tree2, 'parent-id', False),
+ self.added(tree2, 'new'),
+ self.deleted(tree1, 'old'),
+ self.renamed(tree1, tree2, 'file-id', True)],
+ self.do_iter_changes(tree1, tree2,
+ specific_files=['changed/unchanging/file']))
+
+ def test_specific_with_rename_under_new_dir_reports_new_dir(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_7(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ # d(d-id) is new, e is b-id renamed.
+ root_id = tree1.path2id('')
+ self.assertEqualIterChanges(
+ [self.renamed(tree1, tree2, 'b-id', False),
+ self.added(tree2, 'd-id')],
+ self.do_iter_changes(tree1, tree2, specific_files=['d/e']))
+
+ def test_specific_with_rename_under_dir_under_new_dir_reports_new_dir(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_7(tree2)
+ tree2.rename_one('a', 'd/e/a')
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ # d is new, d/e is b-id renamed, d/e/a is a-id renamed
+ root_id = tree1.path2id('')
+ self.assertEqualIterChanges(
+ [self.renamed(tree1, tree2, 'b-id', False),
+ self.added(tree2, 'd-id'),
+ self.renamed(tree1, tree2, 'a-id', False)],
+ self.do_iter_changes(tree1, tree2, specific_files=['d/e/a']))
+
+ def test_specific_old_parent_same_path_new_parent(self):
+ # when a parent is new at its path, if the path was used in the source
+ # it must be emitted as a change.
+ tree1 = self.make_branch_and_tree('1')
+ tree1.add(['a'], ['a-id'], ['file'])
+ tree1.put_file_bytes_non_atomic('a-id', 'a file')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree2.mkdir('a', 'b-id')
+ tree2.add(['a/c'], ['c-id'], ['file'])
+ tree2.put_file_bytes_non_atomic('c-id', 'another file')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ # a-id is gone, b-id and c-id are added.
+ self.assertEqualIterChanges(
+ [self.deleted(tree1, 'a-id'),
+ self.added(tree2, 'b-id'),
+ self.added(tree2, 'c-id')],
+ self.do_iter_changes(tree1, tree2, specific_files=['a/c']))
+
+ def test_specific_old_parent_becomes_file(self):
+ # When an old parent included because of a path conflict becomes a
+ # non-directory, its children have to be all included in the delta.
+ tree1 = self.make_branch_and_tree('1')
+ tree1.mkdir('a', 'a-old-id')
+ tree1.mkdir('a/reparented', 'reparented-id')
+ tree1.mkdir('a/deleted', 'deleted-id')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree2.mkdir('a', 'a-new-id')
+ tree2.mkdir('a/reparented', 'reparented-id')
+ tree2.add(['b'], ['a-old-id'], ['file'])
+ tree2.put_file_bytes_non_atomic('a-old-id', '')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ # a-old-id is kind-changed, a-new-id is added, reparented-id is renamed,
+ # deleted-id is gone
+ self.assertEqualIterChanges(
+ [self.kind_changed(tree1, tree2, 'a-old-id'),
+ self.added(tree2, 'a-new-id'),
+ self.renamed(tree1, tree2, 'reparented-id', False),
+ self.deleted(tree1, 'deleted-id')],
+ self.do_iter_changes(tree1, tree2,
+ specific_files=['a/reparented']))
+
+ def test_specific_old_parent_is_deleted(self):
+ # When an old parent included because of a path conflict is removed,
+ # its children have to be all included in the delta.
+ tree1 = self.make_branch_and_tree('1')
+ tree1.mkdir('a', 'a-old-id')
+ tree1.mkdir('a/reparented', 'reparented-id')
+ tree1.mkdir('a/deleted', 'deleted-id')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree2.mkdir('a', 'a-new-id')
+ tree2.mkdir('a/reparented', 'reparented-id')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ # a-old-id is gone, a-new-id is added, reparented-id is renamed,
+ # deleted-id is gone
+ self.assertEqualIterChanges(
+ [self.deleted(tree1, 'a-old-id'),
+ self.added(tree2, 'a-new-id'),
+ self.renamed(tree1, tree2, 'reparented-id', False),
+ self.deleted(tree1, 'deleted-id')],
+ self.do_iter_changes(tree1, tree2,
+ specific_files=['a/reparented']))
+
+ def test_specific_old_parent_child_collides_with_unselected_new(self):
+ # When the child of an old parent because of a path conflict becomes a
+ # path conflict with some unselected item in the source, that item also
+ # needs to be included (because otherwise the output of applying the
+ # delta to the source would have two items at that path).
+ tree1 = self.make_branch_and_tree('1')
+ tree1.mkdir('a', 'a-old-id')
+ tree1.mkdir('a/reparented', 'reparented-id')
+ tree1.mkdir('collides', 'collides-id')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree2.mkdir('a', 'a-new-id')
+ tree2.mkdir('a/selected', 'selected-id')
+ tree2.mkdir('collides', 'reparented-id')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ # a-old-id is one, a-new-id is added, reparented-id is renamed,
+ # collides-id is gone, selected-id is new.
+ self.assertEqualIterChanges(
+ [self.deleted(tree1, 'a-old-id'),
+ self.added(tree2, 'a-new-id'),
+ self.renamed(tree1, tree2, 'reparented-id', False),
+ self.deleted(tree1, 'collides-id'),
+ self.added(tree2, 'selected-id')],
+ self.do_iter_changes(tree1, tree2,
+ specific_files=['a/selected']))
+
+ def test_specific_old_parent_child_dir_stops_being_dir(self):
+ # When the child of an old parent also stops being a directory, its
+ # children must also be included. This test checks that downward
+ # recursion is done appropriately by starting at a child of the root of
+ # a deleted subtree (a/reparented), and checking that a sibling
+ # directory (a/deleted) has its children included in the delta.
+ tree1 = self.make_branch_and_tree('1')
+ tree1.mkdir('a', 'a-old-id')
+ tree1.mkdir('a/reparented', 'reparented-id-1')
+ tree1.mkdir('a/deleted', 'deleted-id-1')
+ tree1.mkdir('a/deleted/reparented', 'reparented-id-2')
+ tree1.mkdir('a/deleted/deleted', 'deleted-id-2')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree2.set_root_id(tree1.get_root_id())
+ tree2.mkdir('a', 'a-new-id')
+ tree2.mkdir('a/reparented', 'reparented-id-1')
+ tree2.mkdir('reparented', 'reparented-id-2')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ # a-old-id is gone, a-new-id is added, reparented-id-1, -2 are renamed,
+ # deleted-id-1 and -2 are gone.
+ self.assertEqualIterChanges(
+ [self.deleted(tree1, 'a-old-id'),
+ self.added(tree2, 'a-new-id'),
+ self.renamed(tree1, tree2, 'reparented-id-1', False),
+ self.renamed(tree1, tree2, 'reparented-id-2', False),
+ self.deleted(tree1, 'deleted-id-1'),
+ self.deleted(tree1, 'deleted-id-2')],
+ self.do_iter_changes(tree1, tree2,
+ specific_files=['a/reparented']))
+
+ def test_file_rename_and_meta_modification(self):
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_6(tree2)
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ root_id = tree1.path2id('')
+ self.assertEqual([('c-id', ('b/c', 'e'), False, (True, True),
+ ('b-id', root_id), ('c', 'e'), ('file', 'file'),
+ (False, True))],
+ self.do_iter_changes(tree1, tree2))
+
+ def test_file_becomes_unversionable_bug_438569(self):
+ # This isn't strictly a intertree problem, but its the intertree code
+ # path that triggers all stat cache updates on both xml and dirstate
+ # trees.
+ # In bug 438569, a file becoming a fifo causes an assert. Fifo's are
+ # not versionable or diffable. For now, we simply stop cold when they
+ # are detected (because we don't know how far through the code the
+ # assumption 'fifo's do not exist' goes). In future we could report
+ # the kind change and have commit refuse to go futher, or something
+ # similar. One particular reason for choosing this approach is that
+ # there is no minikind for 'fifo' in dirstate today, so we can't
+ # actually update records that way.
+ # To add confusion, the totally generic code path works - but it
+ # doesn't update persistent metadata. So this test permits InterTrees
+ # to either work, or fail with BadFileKindError.
+ self.requireFeature(features.OsFifoFeature)
+ tree1 = self.make_branch_and_tree('1')
+ self.build_tree(['1/a'])
+ tree1.set_root_id('root-id')
+ tree1.add(['a'], ['a-id'])
+ tree2 = self.make_branch_and_tree('2')
+ os.mkfifo('2/a')
+ tree2.add(['a'], ['a-id'], ['file'])
+ try:
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ except (KeyError,):
+ raise tests.TestNotApplicable(
+ "Cannot represent a FIFO in this case %s" % self.id())
+ try:
+ self.do_iter_changes(tree1, tree2)
+ except errors.BadFileKindError:
+ pass
+
+ def test_missing_in_target(self):
+ """Test with the target files versioned but absent from disk."""
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content(tree2)
+ os.unlink('2/a')
+ shutil.rmtree('2/b')
+ # TODO ? have a symlink here?
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ self.not_applicable_if_missing_in('a', tree2)
+ self.not_applicable_if_missing_in('b', tree2)
+ root_id = tree1.path2id('')
+ expected = sorted([
+ self.missing('a-id', 'a', 'a', root_id, 'file'),
+ self.missing('b-id', 'b', 'b', root_id, 'directory'),
+ self.missing('c-id', 'b/c', 'b/c', 'b-id', 'file'),
+ ])
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
+
+ def test_missing_and_renamed(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree1/file'])
+ tree1.add(['file'], ['file-id'])
+ self.build_tree(['tree2/directory/'])
+ tree2.add(['directory'], ['file-id'])
+ os.rmdir('tree2/directory')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_missing_in('directory', tree2)
+
+ root_id = tree1.path2id('')
+ expected = sorted([
+ self.missing('file-id', 'file', 'directory', root_id, 'file'),
+ ])
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
+
+ def test_only_in_source_and_missing(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree1/file'])
+ tree1.add(['file'], ['file-id'])
+ os.unlink('tree1/file')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_missing_in('file', tree1)
+ root_id = tree1.path2id('')
+ expected = [('file-id', ('file', None), False, (True, False),
+ (root_id, None), ('file', None), (None, None), (False, None))]
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
+
+ def test_only_in_target_and_missing(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree2/file'])
+ tree2.add(['file'], ['file-id'])
+ os.unlink('tree2/file')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_missing_in('file', tree2)
+ root_id = tree1.path2id('')
+ expected = [('file-id', (None, 'file'), False, (False, True),
+ (None, root_id), (None, 'file'), (None, None), (None, False))]
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
+
+ def test_only_in_target_missing_subtree_specific_bug_367632(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree2/a-dir/', 'tree2/a-dir/a-file'])
+ tree2.add(['a-dir', 'a-dir/a-file'], ['dir-id', 'file-id'])
+ os.unlink('tree2/a-dir/a-file')
+ os.rmdir('tree2/a-dir')
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_missing_in('a-dir', tree2)
+ root_id = tree1.path2id('')
+ expected = [
+ ('dir-id', (None, 'a-dir'), False, (False, True),
+ (None, root_id), (None, 'a-dir'), (None, None), (None, False)),
+ ('file-id', (None, 'a-dir/a-file'), False, (False, True),
+ (None, 'dir-id'), (None, 'a-file'), (None, None), (None, False))
+ ]
+ # bug 367632 showed that specifying the root broke some code paths,
+ # so we check this contract with and without it.
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2, specific_files=['']))
+
+ def test_unchanged_with_renames_and_modifications(self):
+ """want_unchanged should generate a list of unchanged entries."""
+ tree1 = self.make_branch_and_tree('1')
+ tree2 = self.make_to_branch_and_tree('2')
+ tree1 = self.get_tree_no_parents_abc_content(tree1)
+ tree2 = self.get_tree_no_parents_abc_content_5(tree2)
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ root_id = tree1.path2id('')
+ self.assertEqual(sorted([self.unchanged(tree1, root_id),
+ self.unchanged(tree1, 'b-id'),
+ ('a-id', ('a', 'd'), True, (True, True),
+ (root_id, root_id), ('a', 'd'), ('file', 'file'),
+ (False, False)), self.unchanged(tree1, 'c-id')]),
+ self.do_iter_changes(tree1, tree2, include_unchanged=True))
+
+ def test_compare_subtrees(self):
+ tree1 = self.make_branch_and_tree('1')
+ if not tree1.supports_tree_reference():
+ return
+ tree1.set_root_id('root-id')
+ subtree1 = self.make_branch_and_tree('1/sub')
+ subtree1.set_root_id('subtree-id')
+ tree1.add_reference(subtree1)
+
+ tree2 = self.make_to_branch_and_tree('2')
+ if not tree2.supports_tree_reference():
+ return
+ tree2.set_root_id('root-id')
+ subtree2 = self.make_to_branch_and_tree('2/sub')
+ subtree2.set_root_id('subtree-id')
+ tree2.add_reference(subtree2)
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+
+ self.assertEqual([], list(tree2.iter_changes(tree1)))
+ subtree1.commit('commit', rev_id='commit-a')
+ self.assertEqual([
+ ('root-id',
+ (u'', u''),
+ False,
+ (True, True),
+ (None, None),
+ (u'', u''),
+ ('directory', 'directory'),
+ (False, False)),
+ ('subtree-id',
+ ('sub', 'sub',),
+ False,
+ (True, True),
+ ('root-id', 'root-id'),
+ ('sub', 'sub'),
+ ('tree-reference', 'tree-reference'),
+ (False, False))],
+ list(tree2.iter_changes(tree1,
+ include_unchanged=True)))
+
+ def test_disk_in_subtrees_skipped(self):
+ """subtrees are considered not-in-the-current-tree.
+
+ This test tests the trivial case, where the basis has no paths in the
+ current trees subtree.
+ """
+ tree1 = self.make_branch_and_tree('1')
+ tree1.set_root_id('root-id')
+ tree2 = self.make_to_branch_and_tree('2')
+ if not tree2.supports_tree_reference():
+ return
+ tree2.set_root_id('root-id')
+ subtree2 = self.make_to_branch_and_tree('2/sub')
+ subtree2.set_root_id('subtree-id')
+ tree2.add_reference(subtree2)
+ self.build_tree(['2/sub/file'])
+ subtree2.add(['file'])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ # this should filter correctly from above
+ self.assertEqual([self.added(tree2, 'subtree-id')],
+ self.do_iter_changes(tree1, tree2, want_unversioned=True))
+ # and when the path is named
+ self.assertEqual([self.added(tree2, 'subtree-id')],
+ self.do_iter_changes(tree1, tree2, specific_files=['sub'],
+ want_unversioned=True))
+
+ def test_default_ignores_unversioned_files(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree1/a', 'tree1/c',
+ 'tree2/a', 'tree2/b', 'tree2/c'])
+ tree1.add(['a', 'c'], ['a-id', 'c-id'])
+ tree2.add(['a', 'c'], ['a-id', 'c-id'])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+
+ # We should ignore the fact that 'b' exists in tree-2
+ # because the want_unversioned parameter was not given.
+ expected = sorted([
+ self.content_changed(tree2, 'a-id'),
+ self.content_changed(tree2, 'c-id'),
+ ])
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_unversioned_paths_in_tree(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree2/file', 'tree2/dir/'])
+ if has_symlinks():
+ os.symlink('target', 'tree2/link')
+ links_supported = True
+ else:
+ links_supported = False
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_cannot_represent_unversioned(tree2)
+ expected = [
+ self.unversioned(tree2, 'file'),
+ self.unversioned(tree2, 'dir'),
+ ]
+ if links_supported:
+ expected.append(self.unversioned(tree2, 'link'))
+ expected = sorted(expected)
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
+ want_unversioned=True))
+
+ def test_unversioned_paths_in_tree_specific_files(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ self.build_tree(['tree2/file', 'tree2/dir/'])
+ if has_symlinks():
+ os.symlink('target', 'tree2/link')
+ links_supported = True
+ else:
+ links_supported = False
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_cannot_represent_unversioned(tree2)
+ expected = [
+ self.unversioned(tree2, 'file'),
+ self.unversioned(tree2, 'dir'),
+ ]
+ specific_files=['file', 'dir']
+ if links_supported:
+ expected.append(self.unversioned(tree2, 'link'))
+ specific_files.append('link')
+ expected = sorted(expected)
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
+ specific_files=specific_files, require_versioned=False,
+ want_unversioned=True))
+
+ def test_unversioned_paths_in_target_matching_source_old_names(self):
+ # its likely that naive implementations of unversioned file support
+ # will fail if the path was versioned, but is not any more,
+ # due to a rename, not due to unversioning it.
+ # That is, if the old tree has a versioned file 'foo', and
+ # the new tree has the same file but versioned as 'bar', and also
+ # has an unknown file 'foo', we should get back output for
+ # both foo and bar.
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree2/file', 'tree2/dir/',
+ 'tree1/file', 'tree2/movedfile',
+ 'tree1/dir/', 'tree2/moveddir/'])
+ if has_symlinks():
+ os.symlink('target', 'tree1/link')
+ os.symlink('target', 'tree2/link')
+ os.symlink('target', 'tree2/movedlink')
+ links_supported = True
+ else:
+ links_supported = False
+ tree1.add(['file', 'dir'], ['file-id', 'dir-id'])
+ tree2.add(['movedfile', 'moveddir'], ['file-id', 'dir-id'])
+ if links_supported:
+ tree1.add(['link'], ['link-id'])
+ tree2.add(['movedlink'], ['link-id'])
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_cannot_represent_unversioned(tree2)
+ root_id = tree1.path2id('')
+ expected = [
+ self.renamed(tree1, tree2, 'dir-id', False),
+ self.renamed(tree1, tree2, 'file-id', True),
+ self.unversioned(tree2, 'file'),
+ self.unversioned(tree2, 'dir'),
+ ]
+ specific_files=['file', 'dir']
+ if links_supported:
+ expected.append(self.renamed(tree1, tree2, 'link-id', False))
+ expected.append(self.unversioned(tree2, 'link'))
+ specific_files.append('link')
+ expected = sorted(expected)
+ # run once with, and once without specific files, to catch
+ # potentially different code paths.
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
+ require_versioned=False,
+ want_unversioned=True))
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
+ specific_files=specific_files, require_versioned=False,
+ want_unversioned=True))
+
+ def test_similar_filenames(self):
+ """Test when we have a few files with similar names."""
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+
+ # The trees are actually identical, but they happen to contain
+ # similarly named files.
+ self.build_tree(['tree1/a/',
+ 'tree1/a/b/',
+ 'tree1/a/b/c/',
+ 'tree1/a/b/c/d/',
+ 'tree1/a-c/',
+ 'tree1/a-c/e/',
+ 'tree2/a/',
+ 'tree2/a/b/',
+ 'tree2/a/b/c/',
+ 'tree2/a/b/c/d/',
+ 'tree2/a-c/',
+ 'tree2/a-c/e/',
+ ])
+ tree1.add(['a', 'a/b', 'a/b/c', 'a/b/c/d', 'a-c', 'a-c/e'],
+ ['a-id', 'b-id', 'c-id', 'd-id', 'a-c-id', 'e-id'])
+ tree2.add(['a', 'a/b', 'a/b/c', 'a/b/c/d', 'a-c', 'a-c/e'],
+ ['a-id', 'b-id', 'c-id', 'd-id', 'a-c-id', 'e-id'])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_cannot_represent_unversioned(tree2)
+
+ self.assertEqual([], self.do_iter_changes(tree1, tree2,
+ want_unversioned=True))
+ expected = sorted([
+ self.unchanged(tree2, tree2.get_root_id()),
+ self.unchanged(tree2, 'a-id'),
+ self.unchanged(tree2, 'b-id'),
+ self.unchanged(tree2, 'c-id'),
+ self.unchanged(tree2, 'd-id'),
+ self.unchanged(tree2, 'a-c-id'),
+ self.unchanged(tree2, 'e-id'),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2,
+ want_unversioned=True,
+ include_unchanged=True))
+
+
+ def test_unversioned_subtree_only_emits_root(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree2/dir/', 'tree2/dir/file'])
+ tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
+ self.not_applicable_if_cannot_represent_unversioned(tree2)
+ expected = [
+ self.unversioned(tree2, 'dir'),
+ ]
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
+ want_unversioned=True))
+
+ def make_trees_with_symlinks(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree1/fromfile', 'tree1/fromdir/'])
+ self.build_tree(['tree2/tofile', 'tree2/todir/', 'tree2/unknown'])
+ os.symlink('original', 'tree1/changed')
+ os.symlink('original', 'tree1/removed')
+ os.symlink('original', 'tree1/tofile')
+ os.symlink('original', 'tree1/todir')
+ # we make the unchanged link point at unknown to catch incorrect
+ # symlink-following code in the specified_files test.
+ os.symlink('unknown', 'tree1/unchanged')
+ os.symlink('new', 'tree2/added')
+ os.symlink('new', 'tree2/changed')
+ os.symlink('new', 'tree2/fromfile')
+ os.symlink('new', 'tree2/fromdir')
+ os.symlink('unknown', 'tree2/unchanged')
+ from_paths_and_ids = [
+ 'fromdir',
+ 'fromfile',
+ 'changed',
+ 'removed',
+ 'todir',
+ 'tofile',
+ 'unchanged',
+ ]
+ to_paths_and_ids = [
+ 'added',
+ 'fromdir',
+ 'fromfile',
+ 'changed',
+ 'todir',
+ 'tofile',
+ 'unchanged',
+ ]
+ tree1.add(from_paths_and_ids, from_paths_and_ids)
+ tree2.add(to_paths_and_ids, to_paths_and_ids)
+ return self.mutable_trees_to_locked_test_trees(tree1, tree2)
+
+ def test_versioned_symlinks(self):
+ self.requireFeature(features.SymlinkFeature)
+ tree1, tree2 = self.make_trees_with_symlinks()
+ self.not_applicable_if_cannot_represent_unversioned(tree2)
+ root_id = tree1.path2id('')
+ expected = [
+ self.unchanged(tree1, tree1.path2id('')),
+ self.added(tree2, 'added'),
+ self.content_changed(tree2, 'changed'),
+ self.kind_changed(tree1, tree2, 'fromdir'),
+ self.kind_changed(tree1, tree2, 'fromfile'),
+ self.deleted(tree1, 'removed'),
+ self.unchanged(tree2, 'unchanged'),
+ self.unversioned(tree2, 'unknown'),
+ self.kind_changed(tree1, tree2, 'todir'),
+ self.kind_changed(tree1, tree2, 'tofile'),
+ ]
+ expected = sorted(expected)
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2, include_unchanged=True,
+ want_unversioned=True))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_versioned_symlinks_specific_files(self):
+ self.requireFeature(features.SymlinkFeature)
+ tree1, tree2 = self.make_trees_with_symlinks()
+ root_id = tree1.path2id('')
+ expected = [
+ self.added(tree2, 'added'),
+ self.content_changed(tree2, 'changed'),
+ self.kind_changed(tree1, tree2, 'fromdir'),
+ self.kind_changed(tree1, tree2, 'fromfile'),
+ self.deleted(tree1, 'removed'),
+ self.kind_changed(tree1, tree2, 'todir'),
+ self.kind_changed(tree1, tree2, 'tofile'),
+ ]
+ expected = sorted(expected)
+ # we should get back just the changed links. We pass in 'unchanged' to
+ # make sure that it is correctly not returned - and neither is the
+ # unknown path 'unknown' which it points at.
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
+ specific_files=['added', 'changed', 'fromdir', 'fromfile',
+ 'removed', 'unchanged', 'todir', 'tofile']))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_tree_with_special_names(self):
+ tree1, tree2, paths, path_ids = self.make_tree_with_special_names()
+ expected = sorted(self.added(tree2, f_id) for f_id in path_ids)
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_trees_with_special_names(self):
+ tree1, tree2, paths, path_ids = self.make_trees_with_special_names()
+ expected = sorted(self.content_changed(tree2, f_id) for f_id in path_ids
+ if f_id.endswith('_f-id'))
+ self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_trees_with_deleted_dir(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ tree2.set_root_id(tree1.get_root_id())
+ self.build_tree(['tree1/a', 'tree1/b/', 'tree1/b/c',
+ 'tree1/b/d/', 'tree1/b/d/e', 'tree1/f/', 'tree1/f/g',
+ 'tree2/a', 'tree2/f/', 'tree2/f/g'])
+ tree1.add(['a', 'b', 'b/c', 'b/d/', 'b/d/e', 'f', 'f/g'],
+ ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id', 'g-id'])
+ tree2.add(['a', 'f', 'f/g'], ['a-id', 'f-id', 'g-id'])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ # We should notice that 'b' and all its children are deleted
+ expected = [
+ self.content_changed(tree2, 'a-id'),
+ self.content_changed(tree2, 'g-id'),
+ self.deleted(tree1, 'b-id'),
+ self.deleted(tree1, 'c-id'),
+ self.deleted(tree1, 'd-id'),
+ self.deleted(tree1, 'e-id'),
+ ]
+ self.assertEqualIterChanges(expected,
+ self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_added_unicode(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+
+ # u'\u03b1' == GREEK SMALL LETTER ALPHA
+ # u'\u03c9' == GREEK SMALL LETTER OMEGA
+ a_id = u'\u03b1-id'.encode('utf8')
+ added_id = u'\u03c9_added_id'.encode('utf8')
+ try:
+ self.build_tree([u'tree1/\u03b1/',
+ u'tree2/\u03b1/',
+ u'tree2/\u03b1/\u03c9-added',
+ ])
+ except UnicodeError:
+ raise tests.TestSkipped("Could not create Unicode files.")
+ tree1.add([u'\u03b1'], [a_id])
+ tree2.add([u'\u03b1', u'\u03b1/\u03c9-added'], [a_id, added_id])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+
+ self.assertEqual([self.added(tree2, added_id)],
+ self.do_iter_changes(tree1, tree2))
+ self.assertEqual([self.added(tree2, added_id)],
+ self.do_iter_changes(tree1, tree2,
+ specific_files=[u'\u03b1']))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_deleted_unicode(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+
+ # u'\u03b1' == GREEK SMALL LETTER ALPHA
+ # u'\u03c9' == GREEK SMALL LETTER OMEGA
+ a_id = u'\u03b1-id'.encode('utf8')
+ deleted_id = u'\u03c9_deleted_id'.encode('utf8')
+ try:
+ self.build_tree([u'tree1/\u03b1/',
+ u'tree1/\u03b1/\u03c9-deleted',
+ u'tree2/\u03b1/',
+ ])
+ except UnicodeError:
+ raise tests.TestSkipped("Could not create Unicode files.")
+ tree1.add([u'\u03b1', u'\u03b1/\u03c9-deleted'], [a_id, deleted_id])
+ tree2.add([u'\u03b1'], [a_id])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+
+ self.assertEqual([self.deleted(tree1, deleted_id)],
+ self.do_iter_changes(tree1, tree2))
+ self.assertEqual([self.deleted(tree1, deleted_id)],
+ self.do_iter_changes(tree1, tree2,
+ specific_files=[u'\u03b1']))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_modified_unicode(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+
+ # u'\u03b1' == GREEK SMALL LETTER ALPHA
+ # u'\u03c9' == GREEK SMALL LETTER OMEGA
+ a_id = u'\u03b1-id'.encode('utf8')
+ mod_id = u'\u03c9_mod_id'.encode('utf8')
+ try:
+ self.build_tree([u'tree1/\u03b1/',
+ u'tree1/\u03b1/\u03c9-modified',
+ u'tree2/\u03b1/',
+ u'tree2/\u03b1/\u03c9-modified',
+ ])
+ except UnicodeError:
+ raise tests.TestSkipped("Could not create Unicode files.")
+ tree1.add([u'\u03b1', u'\u03b1/\u03c9-modified'], [a_id, mod_id])
+ tree2.add([u'\u03b1', u'\u03b1/\u03c9-modified'], [a_id, mod_id])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+
+ self.assertEqual([self.content_changed(tree1, mod_id)],
+ self.do_iter_changes(tree1, tree2))
+ self.assertEqual([self.content_changed(tree1, mod_id)],
+ self.do_iter_changes(tree1, tree2,
+ specific_files=[u'\u03b1']))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_renamed_unicode(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+
+ # u'\u03b1' == GREEK SMALL LETTER ALPHA
+ # u'\u03c9' == GREEK SMALL LETTER OMEGA
+ a_id = u'\u03b1-id'.encode('utf8')
+ rename_id = u'\u03c9_rename_id'.encode('utf8')
+ try:
+ self.build_tree([u'tree1/\u03b1/',
+ u'tree2/\u03b1/',
+ ])
+ except UnicodeError:
+ raise tests.TestSkipped("Could not create Unicode files.")
+ self.build_tree_contents([(u'tree1/\u03c9-source', 'contents\n'),
+ (u'tree2/\u03b1/\u03c9-target', 'contents\n'),
+ ])
+ tree1.add([u'\u03b1', u'\u03c9-source'], [a_id, rename_id])
+ tree2.add([u'\u03b1', u'\u03b1/\u03c9-target'], [a_id, rename_id])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+
+ self.assertEqual([self.renamed(tree1, tree2, rename_id, False)],
+ self.do_iter_changes(tree1, tree2))
+ self.assertEqualIterChanges(
+ [self.renamed(tree1, tree2, rename_id, False)],
+ self.do_iter_changes(tree1, tree2, specific_files=[u'\u03b1']))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_unchanged_unicode(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+ # u'\u03b1' == GREEK SMALL LETTER ALPHA
+ # u'\u03c9' == GREEK SMALL LETTER OMEGA
+ a_id = u'\u03b1-id'.encode('utf8')
+ subfile_id = u'\u03c9-subfile-id'.encode('utf8')
+ rootfile_id = u'\u03c9-root-id'.encode('utf8')
+ try:
+ self.build_tree([u'tree1/\u03b1/',
+ u'tree2/\u03b1/',
+ ])
+ except UnicodeError:
+ raise tests.TestSkipped("Could not create Unicode files.")
+ self.build_tree_contents([
+ (u'tree1/\u03b1/\u03c9-subfile', 'sub contents\n'),
+ (u'tree2/\u03b1/\u03c9-subfile', 'sub contents\n'),
+ (u'tree1/\u03c9-rootfile', 'root contents\n'),
+ (u'tree2/\u03c9-rootfile', 'root contents\n'),
+ ])
+ tree1.add([u'\u03b1', u'\u03b1/\u03c9-subfile', u'\u03c9-rootfile'],
+ [a_id, subfile_id, rootfile_id])
+ tree2.add([u'\u03b1', u'\u03b1/\u03c9-subfile', u'\u03c9-rootfile'],
+ [a_id, subfile_id, rootfile_id])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+
+ expected = sorted([
+ self.unchanged(tree1, root_id),
+ self.unchanged(tree1, a_id),
+ self.unchanged(tree1, subfile_id),
+ self.unchanged(tree1, rootfile_id),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2,
+ include_unchanged=True))
+
+ # We should also be able to select just a subset
+ expected = sorted([
+ self.unchanged(tree1, a_id),
+ self.unchanged(tree1, subfile_id),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2, specific_files=[u'\u03b1'],
+ include_unchanged=True))
+
+ def test_unknown_unicode(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+ # u'\u03b1' == GREEK SMALL LETTER ALPHA
+ # u'\u03c9' == GREEK SMALL LETTER OMEGA
+ a_id = u'\u03b1-id'.encode('utf8')
+ try:
+ self.build_tree([u'tree1/\u03b1/',
+ u'tree2/\u03b1/',
+ u'tree2/\u03b1/unknown_dir/',
+ u'tree2/\u03b1/unknown_file',
+ u'tree2/\u03b1/unknown_dir/file',
+ u'tree2/\u03c9-unknown_root_file',
+ ])
+ except UnicodeError:
+ raise tests.TestSkipped("Could not create Unicode files.")
+ tree1.add([u'\u03b1'], [a_id])
+ tree2.add([u'\u03b1'], [a_id])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_cannot_represent_unversioned(tree2)
+
+ expected = sorted([
+ self.unversioned(tree2, u'\u03b1/unknown_dir'),
+ self.unversioned(tree2, u'\u03b1/unknown_file'),
+ self.unversioned(tree2, u'\u03c9-unknown_root_file'),
+ # a/unknown_dir/file should not be included because we should not
+ # recurse into unknown_dir
+ # self.unversioned(tree2, 'a/unknown_dir/file'),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2,
+ require_versioned=False,
+ want_unversioned=True))
+ self.assertEqual([], # Without want_unversioned we should get nothing
+ self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(False, tree1, tree2)
+
+ # We should also be able to select just a subset
+ expected = sorted([
+ self.unversioned(tree2, u'\u03b1/unknown_dir'),
+ self.unversioned(tree2, u'\u03b1/unknown_file'),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2,
+ specific_files=[u'\u03b1'],
+ require_versioned=False,
+ want_unversioned=True))
+ self.assertEqual([], # Without want_unversioned we should get nothing
+ self.do_iter_changes(tree1, tree2,
+ specific_files=[u'\u03b1']))
+
+ def test_unknown_empty_dir(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+
+ # Start with 2 identical trees
+ self.build_tree(['tree1/a/', 'tree1/b/',
+ 'tree2/a/', 'tree2/b/'])
+ self.build_tree_contents([('tree1/b/file', 'contents\n'),
+ ('tree2/b/file', 'contents\n')])
+ tree1.add(['a', 'b', 'b/file'], ['a-id', 'b-id', 'b-file-id'])
+ tree2.add(['a', 'b', 'b/file'], ['a-id', 'b-id', 'b-file-id'])
+
+ # Now create some unknowns in tree2
+ # We should find both a/file and a/dir as unknown, but we shouldn't
+ # recurse into a/dir to find that a/dir/subfile is also unknown.
+ self.build_tree(['tree2/a/file', 'tree2/a/dir/', 'tree2/a/dir/subfile'])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_cannot_represent_unversioned(tree2)
+
+ expected = sorted([
+ self.unversioned(tree2, u'a/file'),
+ self.unversioned(tree2, u'a/dir'),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2,
+ require_versioned=False,
+ want_unversioned=True))
+
+ def test_rename_over_deleted(self):
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+
+ # The final changes should be:
+ # touch a b c d
+ # add a b c d
+ # commit
+ # rm a d
+ # mv b a
+ # mv c d
+ self.build_tree_contents([
+ ('tree1/a', 'a contents\n'),
+ ('tree1/b', 'b contents\n'),
+ ('tree1/c', 'c contents\n'),
+ ('tree1/d', 'd contents\n'),
+ ('tree2/a', 'b contents\n'),
+ ('tree2/d', 'c contents\n'),
+ ])
+ tree1.add(['a', 'b', 'c', 'd'], ['a-id', 'b-id', 'c-id', 'd-id'])
+ tree2.add(['a', 'd'], ['b-id', 'c-id'])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+
+ expected = sorted([
+ self.deleted(tree1, 'a-id'),
+ self.deleted(tree1, 'd-id'),
+ self.renamed(tree1, tree2, 'b-id', False),
+ self.renamed(tree1, tree2, 'c-id', False),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2))
+ self.check_has_changes(True, tree1, tree2)
+
+ def test_deleted_and_unknown(self):
+ """Test a file marked removed, but still present on disk."""
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+
+ # The final changes should be:
+ # bzr add a b c
+ # bzr rm --keep b
+ self.build_tree_contents([
+ ('tree1/a', 'a contents\n'),
+ ('tree1/b', 'b contents\n'),
+ ('tree1/c', 'c contents\n'),
+ ('tree2/a', 'a contents\n'),
+ ('tree2/b', 'b contents\n'),
+ ('tree2/c', 'c contents\n'),
+ ])
+ tree1.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
+ tree2.add(['a', 'c'], ['a-id', 'c-id'])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_cannot_represent_unversioned(tree2)
+
+ expected = sorted([
+ self.deleted(tree1, 'b-id'),
+ self.unversioned(tree2, 'b'),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2,
+ want_unversioned=True))
+ expected = sorted([
+ self.deleted(tree1, 'b-id'),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2,
+ want_unversioned=False))
+
+ def test_renamed_and_added(self):
+ """Test when we have renamed a file, and put another in its place."""
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+
+ # The final changes are:
+ # bzr add b c
+ # bzr mv b a
+ # bzr mv c d
+ # bzr add b c
+
+ self.build_tree_contents([
+ ('tree1/b', 'b contents\n'),
+ ('tree1/c', 'c contents\n'),
+ ('tree2/a', 'b contents\n'),
+ ('tree2/b', 'new b contents\n'),
+ ('tree2/c', 'new c contents\n'),
+ ('tree2/d', 'c contents\n'),
+ ])
+ tree1.add(['b', 'c'], ['b1-id', 'c1-id'])
+ tree2.add(['a', 'b', 'c', 'd'], ['b1-id', 'b2-id', 'c2-id', 'c1-id'])
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+
+ expected = sorted([
+ self.renamed(tree1, tree2, 'b1-id', False),
+ self.renamed(tree1, tree2, 'c1-id', False),
+ self.added(tree2, 'b2-id'),
+ self.added(tree2, 'c2-id'),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2,
+ want_unversioned=True))
+
+ def test_renamed_and_unknown(self):
+ """A file was moved on the filesystem, but not in bzr."""
+ tree1 = self.make_branch_and_tree('tree1')
+ tree2 = self.make_to_branch_and_tree('tree2')
+ root_id = tree1.get_root_id()
+ tree2.set_root_id(root_id)
+
+ # The final changes are:
+ # bzr add a b
+ # mv a a2
+
+ self.build_tree_contents([
+ ('tree1/a', 'a contents\n'),
+ ('tree1/b', 'b contents\n'),
+ ('tree2/a', 'a contents\n'),
+ ('tree2/b', 'b contents\n'),
+ ])
+ tree1.add(['a', 'b'], ['a-id', 'b-id'])
+ tree2.add(['a', 'b'], ['a-id', 'b-id'])
+ os.rename('tree2/a', 'tree2/a2')
+
+ tree1, tree2 = self.mutable_trees_to_locked_test_trees(tree1, tree2)
+ self.not_applicable_if_missing_in('a', tree2)
+
+ expected = sorted([
+ self.missing('a-id', 'a', 'a', tree2.get_root_id(), 'file'),
+ self.unversioned(tree2, 'a2'),
+ ])
+ self.assertEqual(expected,
+ self.do_iter_changes(tree1, tree2,
+ want_unversioned=True))