diff options
Diffstat (limited to 'bzrlib/tests/per_repository_vf/test_fileid_involved.py')
-rw-r--r-- | bzrlib/tests/per_repository_vf/test_fileid_involved.py | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/bzrlib/tests/per_repository_vf/test_fileid_involved.py b/bzrlib/tests/per_repository_vf/test_fileid_involved.py new file mode 100644 index 0000000..6f07902 --- /dev/null +++ b/bzrlib/tests/per_repository_vf/test_fileid_involved.py @@ -0,0 +1,432 @@ +# Copyright (C) 2005-2010 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 + +import sys +import time + +from bzrlib import ( + errors, + inventory, + remote, + revision as _mod_revision, + tests, + transform, + ) +from bzrlib.tests.scenarios import load_tests_apply_scenarios +from bzrlib.tests.per_repository_vf import ( + TestCaseWithRepository, + all_repository_vf_format_scenarios, + ) + + +load_tests = load_tests_apply_scenarios + + +class FileIdInvolvedWGhosts(TestCaseWithRepository): + + scenarios = all_repository_vf_format_scenarios() + + def create_branch_with_ghost_text(self): + builder = self.make_branch_builder('ghost') + builder.build_snapshot('A-id', None, [ + ('add', ('', 'root-id', 'directory', None)), + ('add', ('a', 'a-file-id', 'file', 'some content\n'))]) + b = builder.get_branch() + old_rt = b.repository.revision_tree('A-id') + new_inv = inventory.mutable_inventory_from_tree(old_rt) + new_inv.revision_id = 'B-id' + new_inv['a-file-id'].revision = 'ghost-id' + new_rev = _mod_revision.Revision('B-id', + timestamp=time.time(), + timezone=0, + message='Committing against a ghost', + committer='Joe Foo <joe@foo.com>', + properties={}, + parent_ids=('A-id', 'ghost-id'), + ) + b.lock_write() + self.addCleanup(b.unlock) + b.repository.start_write_group() + b.repository.add_revision('B-id', new_rev, new_inv) + self.disable_commit_write_group_paranoia(b.repository) + b.repository.commit_write_group() + return b + + def disable_commit_write_group_paranoia(self, repo): + if isinstance(repo, remote.RemoteRepository): + # We can't easily disable the checks in a remote repo. + repo.abort_write_group() + raise tests.TestSkipped( + "repository format does not support storing revisions with " + "missing texts.") + pack_coll = getattr(repo, '_pack_collection', None) + if pack_coll is not None: + # Monkey-patch the pack collection instance to allow storing + # incomplete revisions. + pack_coll._check_new_inventories = lambda: [] + + def test_file_ids_include_ghosts(self): + b = self.create_branch_with_ghost_text() + repo = b.repository + self.assertEqual( + {'a-file-id':set(['ghost-id'])}, + repo.fileids_altered_by_revision_ids(['B-id'])) + + def test_file_ids_uses_fallbacks(self): + builder = self.make_branch_builder('source', + format=self.bzrdir_format) + repo = builder.get_branch().repository + if not repo._format.supports_external_lookups: + raise tests.TestNotApplicable('format does not support stacking') + builder.start_series() + builder.build_snapshot('A-id', None, [ + ('add', ('', 'root-id', 'directory', None)), + ('add', ('file', 'file-id', 'file', 'contents\n'))]) + builder.build_snapshot('B-id', ['A-id'], [ + ('modify', ('file-id', 'new-content\n'))]) + builder.build_snapshot('C-id', ['B-id'], [ + ('modify', ('file-id', 'yet more content\n'))]) + builder.finish_series() + source_b = builder.get_branch() + source_b.lock_read() + self.addCleanup(source_b.unlock) + base = self.make_branch('base') + base.pull(source_b, stop_revision='B-id') + stacked = self.make_branch('stacked') + stacked.set_stacked_on_url('../base') + stacked.pull(source_b, stop_revision='C-id') + + stacked.lock_read() + self.addCleanup(stacked.unlock) + repo = stacked.repository + keys = {'file-id': set(['A-id'])} + if stacked.repository.supports_rich_root(): + keys['root-id'] = set(['A-id']) + self.assertEqual(keys, repo.fileids_altered_by_revision_ids(['A-id'])) + + +class FileIdInvolvedBase(TestCaseWithRepository): + + def touch(self, tree, filename): + # use the trees transport to not depend on the tree's location or type. + tree.bzrdir.root_transport.append_bytes(filename, "appended line\n") + + def compare_tree_fileids(self, branch, old_rev, new_rev): + old_tree = self.branch.repository.revision_tree(old_rev) + new_tree = self.branch.repository.revision_tree(new_rev) + delta = new_tree.changes_from(old_tree) + + l2 = [id for path, id, kind in delta.added] + \ + [id for oldpath, newpath, id, kind, text_modified, \ + meta_modified in delta.renamed] + \ + [id for path, id, kind, text_modified, meta_modified in \ + delta.modified] + return set(l2) + + +class TestFileIdInvolved(FileIdInvolvedBase): + + scenarios = all_repository_vf_format_scenarios() + + def setUp(self): + super(TestFileIdInvolved, self).setUp() + # create three branches, and merge it + # + # ,-->J------>K (branch2) + # / \ + # A --->B --->C---->D-->G (main) + # \ / / + # '--->E---+---->F (branch1) + + # A changes: + # B changes: 'a-file-id-2006-01-01-abcd' + # C changes: Nothing (perfect merge) + # D changes: 'b-file-id-2006-01-01-defg' + # E changes: 'file-d' + # F changes: 'file-d' + # G changes: 'b-file-id-2006-01-01-defg' + # J changes: 'b-file-id-2006-01-01-defg' + # K changes: 'c-funky<file-id>quiji%bo' + + main_wt = self.make_branch_and_tree('main') + main_branch = main_wt.branch + self.build_tree(["main/a","main/b","main/c"]) + + main_wt.add(['a', 'b', 'c'], ['a-file-id-2006-01-01-abcd', + 'b-file-id-2006-01-01-defg', + 'c-funky<file-id>quiji%bo']) + try: + main_wt.commit("Commit one", rev_id="rev-A") + except errors.IllegalPath: + # TODO: jam 20060701 Consider raising a different exception + # newer formats do support this, and nothin can done to + # correct this test - its not a bug. + if sys.platform == 'win32': + raise tests.TestSkipped('Old repository formats do not' + ' support file ids with <> on win32') + # This is not a known error condition + raise + + #-------- end A ----------- + + bt1 = self.make_branch_and_tree('branch1') + bt1.pull(main_branch) + b1 = bt1.branch + self.build_tree(["branch1/d"]) + bt1.add(['d'], ['file-d']) + bt1.commit("branch1, Commit one", rev_id="rev-E") + + #-------- end E ----------- + + self.touch(main_wt, "a") + main_wt.commit("Commit two", rev_id="rev-B") + + #-------- end B ----------- + + bt2 = self.make_branch_and_tree('branch2') + bt2.pull(main_branch) + branch2_branch = bt2.branch + set_executability(bt2, 'b', True) + bt2.commit("branch2, Commit one", rev_id="rev-J") + + #-------- end J ----------- + + main_wt.merge_from_branch(b1) + main_wt.commit("merge branch1, rev-11", rev_id="rev-C") + + #-------- end C ----------- + + bt1.rename_one("d","e") + bt1.commit("branch1, commit two", rev_id="rev-F") + + #-------- end F ----------- + + self.touch(bt2, "c") + bt2.commit("branch2, commit two", rev_id="rev-K") + + #-------- end K ----------- + + main_wt.merge_from_branch(b1) + self.touch(main_wt, "b") + # D gets some funky characters to make sure the unescaping works + main_wt.commit("merge branch1, rev-12", rev_id="rev-<D>") + + # end D + + main_wt.merge_from_branch(branch2_branch) + main_wt.commit("merge branch1, rev-22", rev_id="rev-G") + + # end G + self.branch = main_branch + + def test_fileids_altered_between_two_revs(self): + self.branch.lock_read() + self.addCleanup(self.branch.unlock) + self.branch.repository.fileids_altered_by_revision_ids(["rev-J","rev-K"]) + self.assertEqual( + {'b-file-id-2006-01-01-defg':set(['rev-J']), + 'c-funky<file-id>quiji%bo':set(['rev-K']) + }, + self.branch.repository.fileids_altered_by_revision_ids(["rev-J","rev-K"])) + + self.assertEqual( + {'b-file-id-2006-01-01-defg': set(['rev-<D>']), + 'file-d': set(['rev-F']), + }, + self.branch.repository.fileids_altered_by_revision_ids(['rev-<D>', 'rev-F'])) + + self.assertEqual( + { + 'b-file-id-2006-01-01-defg': set(['rev-<D>', 'rev-G', 'rev-J']), + 'c-funky<file-id>quiji%bo': set(['rev-K']), + 'file-d': set(['rev-F']), + }, + self.branch.repository.fileids_altered_by_revision_ids( + ['rev-<D>', 'rev-G', 'rev-F', 'rev-K', 'rev-J'])) + + self.assertEqual( + {'a-file-id-2006-01-01-abcd': set(['rev-B']), + 'b-file-id-2006-01-01-defg': set(['rev-<D>', 'rev-G', 'rev-J']), + 'c-funky<file-id>quiji%bo': set(['rev-K']), + 'file-d': set(['rev-F']), + }, + self.branch.repository.fileids_altered_by_revision_ids( + ['rev-G', 'rev-F', 'rev-C', 'rev-B', 'rev-<D>', 'rev-K', 'rev-J'])) + + def fileids_altered_by_revision_ids(self, revision_ids): + """This is a wrapper to strip TREE_ROOT if it occurs""" + repo = self.branch.repository + root_id = self.branch.basis_tree().get_root_id() + result = repo.fileids_altered_by_revision_ids(revision_ids) + if root_id in result: + del result[root_id] + return result + + def test_fileids_altered_by_revision_ids(self): + self.branch.lock_read() + self.addCleanup(self.branch.unlock) + self.assertEqual( + {'a-file-id-2006-01-01-abcd':set(['rev-A']), + 'b-file-id-2006-01-01-defg': set(['rev-A']), + 'c-funky<file-id>quiji%bo': set(['rev-A']), + }, + self.fileids_altered_by_revision_ids(["rev-A"])) + self.assertEqual( + {'a-file-id-2006-01-01-abcd':set(['rev-B']) + }, + self.branch.repository.fileids_altered_by_revision_ids(["rev-B"])) + self.assertEqual( + {'b-file-id-2006-01-01-defg':set(['rev-<D>']) + }, + self.branch.repository.fileids_altered_by_revision_ids(["rev-<D>"])) + + def test_fileids_involved_full_compare(self): + # this tests that the result of each fileid_involved calculation + # along a revision history selects only the fileids selected by + # comparing the trees - no less, and no more. This is correct + # because in our sample data we do not revert any file ids along + # the revision history. + self.branch.lock_read() + self.addCleanup(self.branch.unlock) + pp=[] + graph = self.branch.repository.get_graph() + history = list(graph.iter_lefthand_ancestry(self.branch.last_revision(), + [_mod_revision.NULL_REVISION])) + history.reverse() + + if len(history) < 2: + return + + for start in range(0,len(history)-1): + start_id = history[start] + for end in range(start+1,len(history)): + end_id = history[end] + unique_revs = graph.find_unique_ancestors(end_id, [start_id]) + l1 = self.branch.repository.fileids_altered_by_revision_ids( + unique_revs) + l1 = set(l1.keys()) + l2 = self.compare_tree_fileids(self.branch, start_id, end_id) + self.assertEquals(l1, l2) + + +class TestFileIdInvolvedNonAscii(FileIdInvolvedBase): + + scenarios = all_repository_vf_format_scenarios() + + def test_utf8_file_ids_and_revision_ids(self): + main_wt = self.make_branch_and_tree('main') + main_branch = main_wt.branch + self.build_tree(["main/a"]) + + file_id = u'a-f\xedle-id'.encode('utf8') + main_wt.add(['a'], [file_id]) + revision_id = u'r\xe9v-a'.encode('utf8') + try: + main_wt.commit('a', rev_id=revision_id) + except errors.NonAsciiRevisionId: + raise tests.TestSkipped('non-ascii revision ids not supported by %s' + % self.repository_format) + + repo = main_wt.branch.repository + repo.lock_read() + self.addCleanup(repo.unlock) + file_ids = repo.fileids_altered_by_revision_ids([revision_id]) + root_id = main_wt.basis_tree().get_root_id() + if root_id in file_ids: + self.assertEqual({file_id:set([revision_id]), + root_id:set([revision_id]) + }, file_ids) + else: + self.assertEqual({file_id:set([revision_id])}, file_ids) + + +class TestFileIdInvolvedSuperset(FileIdInvolvedBase): + + scenarios = all_repository_vf_format_scenarios() + + def setUp(self): + super(TestFileIdInvolvedSuperset, self).setUp() + + self.branch = None + main_wt = self.make_branch_and_tree('main') + main_branch = main_wt.branch + self.build_tree(["main/a","main/b","main/c"]) + + main_wt.add(['a', 'b', 'c'], ['a-file-id-2006-01-01-abcd', + 'b-file-id-2006-01-01-defg', + 'c-funky<file-id>quiji\'"%bo']) + try: + main_wt.commit("Commit one", rev_id="rev-A") + except errors.IllegalPath: + # TODO: jam 20060701 Consider raising a different exception + # newer formats do support this, and nothin can done to + # correct this test - its not a bug. + if sys.platform == 'win32': + raise tests.TestSkipped('Old repository formats do not' + ' support file ids with <> on win32') + # This is not a known error condition + raise + + branch2_wt = self.make_branch_and_tree('branch2') + branch2_wt.pull(main_branch) + branch2_bzrdir = branch2_wt.bzrdir + branch2_branch = branch2_bzrdir.open_branch() + set_executability(branch2_wt, 'b', True) + branch2_wt.commit("branch2, Commit one", rev_id="rev-J") + + main_wt.merge_from_branch(branch2_branch) + set_executability(main_wt, 'b', False) + main_wt.commit("merge branch1, rev-22", rev_id="rev-G") + + # end G + self.branch = main_branch + + def test_fileid_involved_full_compare2(self): + # this tests that fileids_altered_by_revision_ids returns + # more information than compare_tree can, because it + # sees each change rather than the aggregate delta. + self.branch.lock_read() + self.addCleanup(self.branch.unlock) + graph = self.branch.repository.get_graph() + history = list(graph.iter_lefthand_ancestry(self.branch.last_revision(), + [_mod_revision.NULL_REVISION])) + history.reverse() + old_rev = history[0] + new_rev = history[1] + unique_revs = graph.find_unique_ancestors(new_rev, [old_rev]) + + l1 = self.branch.repository.fileids_altered_by_revision_ids( + unique_revs) + l1 = set(l1.keys()) + + l2 = self.compare_tree_fileids(self.branch, old_rev, new_rev) + self.assertNotEqual(l2, l1) + self.assertSubset(l2, l1) + + +def set_executability(wt, path, executable=True): + """Set the executable bit for the file at path in the working tree + + os.chmod() doesn't work on windows. But TreeTransform can mark or + unmark a file as executable. + """ + file_id = wt.path2id(path) + tt = transform.TreeTransform(wt) + try: + tt.set_executability(executable, tt.trans_id_tree_file_id(file_id)) + tt.apply() + finally: + tt.finalize() |