Ejemplo n.º 1
0
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().path2id('')
        if root_id in file_ids:
            self.assertEqual({
                file_id: {revision_id},
                root_id: {revision_id}
            }, file_ids)
        else:
            self.assertEqual({file_id: {revision_id}}, file_ids)
Ejemplo n.º 2
0
class TestCallbacks(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def test_callback_tree_and_branch(self):
        # use a real tree to get actual refs that will work
        tree = self.make_branch_and_tree('foo')
        revid = tree.commit('foo')
        tree.lock_read()
        self.addCleanup(tree.unlock)
        needed_refs = {}
        for ref in tree._get_check_refs():
            needed_refs.setdefault(ref, []).append(tree)
        for ref in tree.branch._get_check_refs():
            needed_refs.setdefault(ref, []).append(tree.branch)
        self.tree_check = tree._check
        self.branch_check = tree.branch.check
        self.overrideAttr(tree, "_check", self.tree_callback)
        self.overrideAttr(tree.branch, "check", self.branch_callback)
        self.callbacks = []
        tree.branch.repository.check([revid], callback_refs=needed_refs)
        self.assertNotEqual([], self.callbacks)

    def tree_callback(self, refs):
        self.callbacks.append(('tree', refs))
        return self.tree_check(refs)

    def branch_callback(self, refs):
        self.callbacks.append(('branch', refs))
        return self.branch_check(refs)
Ejemplo n.º 3
0
class TestSource(TestCaseWithRepository):
    """Tests for/about the results of Repository._get_source."""

    scenarios = all_repository_vf_format_scenarios()

    def test_no_absent_records_in_stream_with_ghosts(self):
        # XXX: Arguably should be in per_interrepository but
        # doesn't actually gain coverage there; need a specific set of
        # permutations to cover it.
        # bug lp:376255 was reported about this.
        builder = self.make_branch_builder('repo')
        builder.start_series()
        builder.build_snapshot([b'ghost'],
                               [('add', ('', b'ROOT_ID', 'directory', ''))],
                               allow_leftmost_as_ghost=True,
                               revision_id=b'tip')
        builder.finish_series()
        b = builder.get_branch()
        b.lock_read()
        self.addCleanup(b.unlock)
        repo = b.repository
        source = repo._get_source(repo._format)
        search = vf_search.PendingAncestryResult([b'tip'], repo)
        stream = source.get_stream(search)
        for substream_type, substream in stream:
            for record in substream:
                self.assertNotEqual(
                    'absent', record.storage_kind, "Absent record for %s" %
                    (((substream_type, ) + record.key), ))
Ejemplo n.º 4
0
def broken_scenarios_for_all_formats():
    format_scenarios = all_repository_vf_format_scenarios()
    # test_check_reconcile needs to be parameterized by format *and* by broken
    # repository scenario.
    broken_scenarios = [(s.__name__, {'scenario_class': s})
                        for s in all_broken_scenario_classes]
    return multiply_scenarios(format_scenarios, broken_scenarios)
Ejemplo n.º 5
0
class TestRefreshData(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def fetch_new_revision_into_concurrent_instance(self, repo, token):
        """Create a new revision (revid 'new-rev') and fetch it into a
        concurrent instance of repo.
        """
        source = self.make_branch_and_memory_tree('source')
        source.lock_write()
        self.addCleanup(source.unlock)
        source.add([''], [b'root-id'])
        revid = source.commit('foo', rev_id=b'new-rev')
        # Force data reading on weaves/knits
        repo.all_revision_ids()
        repo.revisions.keys()
        repo.inventories.keys()
        # server repo is the instance a smart server might hold for this
        # repository.
        server_repo = repo.controldir.open_repository()
        try:
            server_repo.lock_write(token)
        except errors.TokenLockingNotSupported:
            self.skipTest('Cannot concurrently insert into repo format %r' %
                          self.repository_format)
        try:
            server_repo.fetch(source.branch.repository, revid)
        finally:
            server_repo.unlock()

    def test_refresh_data_after_fetch_new_data_visible_in_write_group(self):
        tree = self.make_branch_and_memory_tree('target')
        tree.lock_write()
        self.addCleanup(tree.unlock)
        tree.add([''], [b'root-id'])
        tree.commit('foo', rev_id=b'commit-in-target')
        repo = tree.branch.repository
        token = repo.lock_write().repository_token
        self.addCleanup(repo.unlock)
        repo.start_write_group()
        self.addCleanup(repo.abort_write_group)
        self.fetch_new_revision_into_concurrent_instance(repo, token)
        # Call refresh_data.  It either fails with IsInWriteGroupError, or it
        # succeeds and the new revisions are visible.
        try:
            repo.refresh_data()
        except repository.IsInWriteGroupError:
            pass
        else:
            self.assertEqual([b'commit-in-target', b'new-rev'],
                             sorted(repo.all_revision_ids()))

    def test_refresh_data_after_fetch_new_data_visible(self):
        repo = self.make_repository('target')
        token = repo.lock_write().repository_token
        self.addCleanup(repo.unlock)
        self.fetch_new_revision_into_concurrent_instance(repo, token)
        repo.refresh_data()
        self.assertNotEqual({}, repo.get_graph().get_parent_map([b'new-rev']))
Ejemplo n.º 6
0
class TestGenerateTextKeyIndex(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def test_empty(self):
        repo = self.make_repository('.')
        repo.lock_read()
        self.addCleanup(repo.unlock)
        self.assertEqual({}, repo._generate_text_key_index())
Ejemplo n.º 7
0
class TestFindTextKeyReferences(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def test_empty(self):
        repo = self.make_repository('.')
        repo.lock_read()
        self.addCleanup(repo.unlock)
        self.assertEqual({}, repo.find_text_key_references())
Ejemplo n.º 8
0
class TestNoSpuriousInconsistentAncestors(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def test_two_files_different_versions_no_inconsistencies_bug_165071(self):
        """Two files, with different versions can be clean."""
        tree = self.make_branch_and_tree('.')
        self.build_tree(['foo'])
        tree.smart_add(['.'])
        revid1 = tree.commit('1')
        self.build_tree(['bar'])
        tree.smart_add(['.'])
        revid2 = tree.commit('2')
        check_object = tree.branch.repository.check([revid1, revid2])
        check_object.report_results(verbose=True)
        self.assertContainsRe(self.get_log(), "0 unreferenced text versions")
Ejemplo n.º 9
0
class TestFindInconsistentRevisionParents(TestCaseWithBrokenRevisionIndex):

    scenarios = all_repository_vf_format_scenarios()

    def test__find_inconsistent_revision_parents(self):
        """_find_inconsistent_revision_parents finds revisions with broken
        parents.
        """
        repo = self.make_repo_with_extra_ghost_index()
        self.assertEqual([(b'revision-id', (b'incorrect-parent', ), ())],
                         list(repo._find_inconsistent_revision_parents()))

    def test__check_for_inconsistent_revision_parents(self):
        """_check_for_inconsistent_revision_parents raises BzrCheckError if
        there are any revisions with inconsistent parents.
        """
        repo = self.make_repo_with_extra_ghost_index()
        self.assertRaises(errors.BzrCheckError,
                          repo._check_for_inconsistent_revision_parents)

    def test__check_for_inconsistent_revision_parents_on_clean_repo(self):
        """_check_for_inconsistent_revision_parents does nothing if there are
        no broken revisions.
        """
        repo = self.make_repository('empty-repo')
        if not repo._format.revision_graph_can_have_wrong_parents:
            raise TestNotApplicable('%r cannot have corrupt revision index.' %
                                    repo)
        repo.lock_read()
        try:
            repo._check_for_inconsistent_revision_parents()  # nothing happens
        finally:
            repo.unlock()

    def test_check_reports_bad_ancestor(self):
        repo = self.make_repo_with_extra_ghost_index()
        # XXX: check requires a non-empty revision IDs list, but it ignores the
        # contents of it!
        check_object = repo.check(['ignored'])
        check_object.report_results(verbose=False)
        self.assertContainsRe(
            self.get_log(),
            '1 revisions have incorrect parents in the revision index')
        check_object.report_results(verbose=True)
        self.assertContainsRe(
            self.get_log(), "revision-id has wrong parents in index: "
            r"\(incorrect-parent\) should be \(\)")
Ejemplo n.º 10
0
class TestReconcile(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def checkUnreconciled(self, d, reconciler):
        """Check that d did not get reconciled."""
        # nothing should have been fixed yet:
        self.assertEqual(0, reconciler.inconsistent_parents)
        # and no garbage inventories
        self.assertEqual(0, reconciler.garbage_inventories)
        self.checkNoBackupInventory(d)

    def checkNoBackupInventory(self, aBzrDir):
        """Check that there is no backup inventory in aBzrDir."""
        repo = aBzrDir.open_repository()
        for path in repo.control_transport.list_dir('.'):
            self.assertFalse('inventory.backup' in path)
Ejemplo n.º 11
0
class TestMergeDirective(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def make_two_branches(self):
        builder = self.make_branch_builder('source')
        builder.start_series()
        builder.build_snapshot(None, [
            ('add', ('', b'root-id', 'directory', None)),
            ('add', ('f', b'f-id', 'file', b'initial content\n')),
            ], revision_id=b'A')
        builder.build_snapshot([b'A'], [
            ('modify', ('f', b'new content\n')),
            ], revision_id=b'B')
        builder.finish_series()
        b1 = builder.get_branch()
        b2 = b1.controldir.sprout('target', revision_id=b'A').open_branch()
        return b1, b2

    def create_merge_directive(self, source_branch, submit_url):
        return merge_directive.MergeDirective2.from_objects(
            source_branch.repository,
            source_branch.last_revision(),
            time=1247775710, timezone=0,
            target_branch=submit_url)

    def test_create_merge_directive(self):
        source_branch, target_branch = self.make_two_branches()
        directive = self.create_merge_directive(source_branch,
                                                target_branch.base)
        self.assertIsInstance(directive, merge_directive.MergeDirective2)

    def test_create_and_install_directive(self):
        source_branch, target_branch = self.make_two_branches()
        directive = self.create_merge_directive(source_branch,
                                                target_branch.base)
        chk_map.clear_cache()
        directive.install_revisions(target_branch.repository)
        rt = target_branch.repository.revision_tree(b'B')
        with rt.lock_read():
            self.assertEqualDiff(b'new content\n', rt.get_file_text('f'))
Ejemplo n.º 12
0
class TestBadRevisionParents(TestCaseWithBrokenRevisionIndex):

    scenarios = all_repository_vf_format_scenarios()

    def test_aborts_if_bad_parents_in_index(self):
        """Reconcile refuses to proceed if the revision index is wrong when
        checked against the revision texts, so that it does not generate broken
        data.

        Ideally reconcile would fix this, but until we implement that we just
        make sure we safely detect this problem.
        """
        repo = self.make_repo_with_extra_ghost_index()
        result = repo.reconcile(thorough=True)
        self.assertTrue(result.aborted,
                        "reconcile should have aborted due to bad parents.")

    def test_does_not_abort_on_clean_repo(self):
        repo = self.make_repository('.')
        result = repo.reconcile(thorough=True)
        self.assertFalse(result.aborted,
                         "reconcile should not have aborted on an unbroken repository.")
Ejemplo n.º 13
0
class TestGetMissingParentInventories(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def test_empty_get_missing_parent_inventories(self):
        """A new write group has no missing parent inventories."""
        repo = self.make_repository('.')
        repo.lock_write()
        repo.start_write_group()
        try:
            self.assertEqual(set(), set(repo.get_missing_parent_inventories()))
        finally:
            repo.commit_write_group()
            repo.unlock()

    def branch_trunk_and_make_tree(self, trunk_repo, relpath):
        tree = self.make_branch_and_memory_tree('branch')
        trunk_repo.lock_read()
        self.addCleanup(trunk_repo.unlock)
        tree.branch.repository.fetch(trunk_repo, revision_id=b'rev-1')
        tree.set_parent_ids([b'rev-1'])
        return tree

    def make_first_commit(self, repo):
        trunk = repo.controldir.create_branch()
        tree = memorytree.MemoryTree.create_on_branch(trunk)
        tree.lock_write()
        tree.add([''], [b'TREE_ROOT'], ['directory'])
        tree.add(['dir'], [b'dir-id'], ['directory'])
        tree.add(['filename'], [b'file-id'], ['file'])
        tree.put_file_bytes_non_atomic('filename', b'content\n')
        tree.commit('Trunk commit', rev_id=b'rev-0')
        tree.commit('Trunk commit', rev_id=b'rev-1')
        tree.unlock()

    def make_new_commit_in_new_repo(self, trunk_repo, parents=None):
        tree = self.branch_trunk_and_make_tree(trunk_repo, 'branch')
        tree.set_parent_ids(parents)
        tree.commit('Branch commit', rev_id=b'rev-2')
        branch_repo = tree.branch.repository
        branch_repo.lock_read()
        self.addCleanup(branch_repo.unlock)
        return branch_repo

    def make_stackable_repo(self, relpath='trunk'):
        if isinstance(self.repository_format, remote.RemoteRepositoryFormat):
            # RemoteRepository by default builds a default format real
            # repository, but the default format is unstackble.  So explicitly
            # make a stackable real repository and use that.
            repo = self.make_repository(relpath, format='1.9')
            dir = controldir.ControlDir.open(self.get_url(relpath))
            repo = dir.open_repository()
        else:
            repo = self.make_repository(relpath)
        if not repo._format.supports_external_lookups:
            raise tests.TestNotApplicable('format not stackable')
        repo.controldir._format.set_branch_format(bzrbranch.BzrBranchFormat7())
        return repo

    def reopen_repo_and_resume_write_group(self, repo):
        try:
            resume_tokens = repo.suspend_write_group()
        except errors.UnsuspendableWriteGroup:
            # If we got this far, and this repo does not support resuming write
            # groups, then get_missing_parent_inventories works in all
            # cases this repo supports.
            repo.unlock()
            return
        repo.unlock()
        reopened_repo = repo.controldir.open_repository()
        reopened_repo.lock_write()
        self.addCleanup(reopened_repo.unlock)
        reopened_repo.resume_write_group(resume_tokens)
        return reopened_repo

    def test_ghost_revision(self):
        """A parent inventory may be absent if all the needed texts are present.
        i.e., a ghost revision isn't (necessarily) considered to be a missing
        parent inventory.
        """
        # Make a trunk with one commit.
        trunk_repo = self.make_stackable_repo()
        self.make_first_commit(trunk_repo)
        trunk_repo.lock_read()
        self.addCleanup(trunk_repo.unlock)
        # Branch the trunk, add a new commit.
        branch_repo = self.make_new_commit_in_new_repo(
            trunk_repo, parents=[b'rev-1', b'ghost-rev'])
        inv = branch_repo.get_inventory(b'rev-2')
        # Make a new repo stacked on trunk, and then copy into it:
        #  - all texts in rev-2
        #  - the new inventory (rev-2)
        #  - the new revision (rev-2)
        repo = self.make_stackable_repo('stacked')
        repo.lock_write()
        repo.start_write_group()
        # Add all texts from in rev-2 inventory.  Note that this has to exclude
        # the root if the repo format does not support rich roots.
        rich_root = branch_repo._format.rich_root_data
        all_texts = [
            (ie.file_id, ie.revision) for ie in inv.iter_just_entries()
            if rich_root or inv.id2path(ie.file_id) != '']
        repo.texts.insert_record_stream(
            branch_repo.texts.get_record_stream(all_texts, 'unordered', False))
        # Add inventory and revision for rev-2.
        repo.add_inventory(b'rev-2', inv, [b'rev-1', b'ghost-rev'])
        repo.revisions.insert_record_stream(
            branch_repo.revisions.get_record_stream(
                [(b'rev-2',)], 'unordered', False))
        # Now, no inventories are reported as missing, even though there is a
        # ghost.
        self.assertEqual(set(), repo.get_missing_parent_inventories())
        # Resuming the write group does not affect
        # get_missing_parent_inventories.
        reopened_repo = self.reopen_repo_and_resume_write_group(repo)
        self.assertEqual(set(), reopened_repo.get_missing_parent_inventories())
        reopened_repo.abort_write_group()

    def test_get_missing_parent_inventories(self):
        """A stacked repo with a single revision and inventory (no parent
        inventory) in it must have all the texts in its inventory (even if not
        changed w.r.t. to the absent parent), otherwise it will report missing
        texts/parent inventory.

        The core of this test is that a file was changed in rev-1, but in a
        stacked repo that only has rev-2
        """
        # Make a trunk with one commit.
        trunk_repo = self.make_stackable_repo()
        self.make_first_commit(trunk_repo)
        trunk_repo.lock_read()
        self.addCleanup(trunk_repo.unlock)
        # Branch the trunk, add a new commit.
        branch_repo = self.make_new_commit_in_new_repo(
            trunk_repo, parents=[b'rev-1'])
        inv = branch_repo.get_inventory(b'rev-2')
        # Make a new repo stacked on trunk, and copy the new commit's revision
        # and inventory records to it.
        repo = self.make_stackable_repo('stacked')
        repo.lock_write()
        repo.start_write_group()
        # Insert a single fulltext inv (using add_inventory because it's
        # simpler than insert_record_stream)
        repo.add_inventory(b'rev-2', inv, [b'rev-1'])
        repo.revisions.insert_record_stream(
            branch_repo.revisions.get_record_stream(
                [(b'rev-2',)], 'unordered', False))
        # There should be no missing compression parents
        self.assertEqual(set(),
                         repo.inventories.get_missing_compression_parent_keys())
        self.assertEqual(
            {('inventories', b'rev-1')},
            repo.get_missing_parent_inventories())
        # Resuming the write group does not affect
        # get_missing_parent_inventories.
        reopened_repo = self.reopen_repo_and_resume_write_group(repo)
        self.assertEqual(
            {('inventories', b'rev-1')},
            reopened_repo.get_missing_parent_inventories())
        # Adding the parent inventory satisfies get_missing_parent_inventories.
        reopened_repo.inventories.insert_record_stream(
            branch_repo.inventories.get_record_stream(
                [(b'rev-1',)], 'unordered', False))
        self.assertEqual(
            set(), reopened_repo.get_missing_parent_inventories())
        reopened_repo.abort_write_group()

    def test_get_missing_parent_inventories_check(self):
        builder = self.make_branch_builder('test')
        builder.build_snapshot([b'ghost-parent-id'], [
            ('add', ('', b'root-id', 'directory', None)),
            ('add', ('file', b'file-id', 'file', b'content\n'))],
            allow_leftmost_as_ghost=True, revision_id=b'A-id')
        b = builder.get_branch()
        b.lock_read()
        self.addCleanup(b.unlock)
        repo = self.make_repository('test-repo')
        repo.lock_write()
        self.addCleanup(repo.unlock)
        repo.start_write_group()
        self.addCleanup(repo.abort_write_group)
        # Now, add the objects manually
        text_keys = [(b'file-id', b'A-id')]
        if repo.supports_rich_root():
            text_keys.append((b'root-id', b'A-id'))
        # Directly add the texts, inventory, and revision object for b'A-id'
        repo.texts.insert_record_stream(b.repository.texts.get_record_stream(
            text_keys, 'unordered', True))
        repo.add_revision(b'A-id', b.repository.get_revision(b'A-id'),
                          b.repository.get_inventory(b'A-id'))
        get_missing = repo.get_missing_parent_inventories
        if repo._format.supports_external_lookups:
            self.assertEqual({('inventories', b'ghost-parent-id')},
                             get_missing(check_for_missing_texts=False))
            self.assertEqual(set(), get_missing(check_for_missing_texts=True))
            self.assertEqual(set(), get_missing())
        else:
            # If we don't support external lookups, we always return empty
            self.assertEqual(set(), get_missing(check_for_missing_texts=False))
            self.assertEqual(set(), get_missing(check_for_missing_texts=True))
            self.assertEqual(set(), get_missing())

    def test_insert_stream_passes_resume_info(self):
        repo = self.make_repository('test-repo')
        if (not repo._format.supports_external_lookups or
                isinstance(repo, remote.RemoteRepository)):
            raise tests.TestNotApplicable(
                'only valid for direct connections to resumable repos')
        # log calls to get_missing_parent_inventories, so that we can assert it
        # is called with the correct parameters
        call_log = []
        orig = repo.get_missing_parent_inventories

        def get_missing(check_for_missing_texts=True):
            call_log.append(check_for_missing_texts)
            return orig(check_for_missing_texts=check_for_missing_texts)
        repo.get_missing_parent_inventories = get_missing
        repo.lock_write()
        self.addCleanup(repo.unlock)
        sink = repo._get_sink()
        sink.insert_stream((), repo._format, [])
        self.assertEqual([False], call_log)
        del call_log[:]
        repo.start_write_group()
        # We need to insert something, or suspend_write_group won't actually
        # create a token
        repo.texts.insert_record_stream([versionedfile.FulltextContentFactory(
            (b'file-id', b'rev-id'), (), None, b'lines\n')])
        tokens = repo.suspend_write_group()
        self.assertNotEqual([], tokens)
        sink.insert_stream((), repo._format, tokens)
        self.assertEqual([True], call_log)

    def test_insert_stream_without_locking_fails_without_lock(self):
        repo = self.make_repository('test-repo')
        sink = repo._get_sink()
        stream = [('texts', [versionedfile.FulltextContentFactory(
            (b'file-id', b'rev-id'), (), None, b'lines\n')])]
        self.assertRaises(errors.ObjectNotLocked,
                          sink.insert_stream_without_locking, stream, repo._format)

    def test_insert_stream_without_locking_fails_without_write_group(self):
        repo = self.make_repository('test-repo')
        self.addCleanup(repo.lock_write().unlock)
        sink = repo._get_sink()
        stream = [('texts', [versionedfile.FulltextContentFactory(
            (b'file-id', b'rev-id'), (), None, b'lines\n')])]
        self.assertRaises(errors.BzrError,
                          sink.insert_stream_without_locking, stream, repo._format)

    def test_insert_stream_without_locking(self):
        repo = self.make_repository('test-repo')
        self.addCleanup(repo.lock_write().unlock)
        repo.start_write_group()
        sink = repo._get_sink()
        stream = [('texts', [versionedfile.FulltextContentFactory(
            (b'file-id', b'rev-id'), (), None, b'lines\n')])]
        missing_keys = sink.insert_stream_without_locking(stream, repo._format)
        repo.commit_write_group()
        self.assertEqual(set(), missing_keys)
Ejemplo n.º 14
0
class TestCaseWithComplexRepository(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def setUp(self):
        super(TestCaseWithComplexRepository, self).setUp()
        tree_a = self.make_branch_and_tree('a')
        self.controldir = tree_a.branch.controldir
        # add a corrupt inventory 'orphan'
        # this may need some generalising for knits.
        tree_a.lock_write()
        try:
            tree_a.branch.repository.start_write_group()
            try:
                inv_file = tree_a.branch.repository.inventories
                inv_file.add_lines((b'orphan', ), [], [])
            except:
                tree_a.branch.repository.commit_write_group()
                raise
            else:
                tree_a.branch.repository.abort_write_group()
        finally:
            tree_a.unlock()
        # add a real revision 'rev1'
        tree_a.commit('rev1', rev_id=b'rev1', allow_pointless=True)
        # add a real revision 'rev2' based on rev1
        tree_a.commit('rev2', rev_id=b'rev2', allow_pointless=True)
        # add a reference to a ghost
        tree_a.add_parent_tree_id(b'ghost1')
        try:
            tree_a.commit('rev3', rev_id=b'rev3', allow_pointless=True)
        except errors.RevisionNotPresent:
            raise tests.TestNotApplicable(
                "Cannot test with ghosts for this format.")
        # add another reference to a ghost, and a second ghost.
        tree_a.add_parent_tree_id(b'ghost1')
        tree_a.add_parent_tree_id(b'ghost2')
        tree_a.commit('rev4', rev_id=b'rev4', allow_pointless=True)

    def test_revision_trees(self):
        revision_ids = [b'rev1', b'rev2', b'rev3', b'rev4']
        repository = self.controldir.open_repository()
        repository.lock_read()
        self.addCleanup(repository.unlock)
        trees1 = list(repository.revision_trees(revision_ids))
        trees2 = [repository.revision_tree(t) for t in revision_ids]
        self.assertEqual(len(trees1), len(trees2))
        for tree1, tree2 in zip(trees1, trees2):
            self.assertFalse(tree2.changes_from(tree1).has_changed())

    def test_get_deltas_for_revisions(self):
        repository = self.controldir.open_repository()
        repository.lock_read()
        self.addCleanup(repository.unlock)
        revisions = [
            repository.get_revision(r)
            for r in [b'rev1', b'rev2', b'rev3', b'rev4']
        ]
        deltas1 = list(repository.get_deltas_for_revisions(revisions))
        deltas2 = [
            repository.get_revision_delta(r.revision_id) for r in revisions
        ]
        self.assertEqual(deltas1, deltas2)

    def test_all_revision_ids(self):
        # all_revision_ids -> all revisions
        self.assertEqual(
            {b'rev1', b'rev2', b'rev3', b'rev4'},
            set(self.controldir.open_repository().all_revision_ids()))

    def test_reserved_id(self):
        repo = self.make_repository('repository')
        repo.lock_write()
        repo.start_write_group()
        try:
            self.assertRaises(errors.ReservedId, repo.add_inventory,
                              b'reserved:', None, None)
            self.assertRaises(errors.ReservedId, repo.add_inventory_by_delta,
                              "foo", [], b'reserved:', None)
            self.assertRaises(errors.ReservedId, repo.add_revision,
                              b'reserved:', None)
        finally:
            repo.abort_write_group()
            repo.unlock()
Ejemplo n.º 15
0
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(
            None, [('add', ('', b'root-id', 'directory', None)),
                   ('add', ('a', b'a-file-id', 'file', b'some content\n'))],
            revision_id=b'A-id')
        b = builder.get_branch()
        old_rt = b.repository.revision_tree(b'A-id')
        new_inv = inventory.mutable_inventory_from_tree(old_rt)
        new_inv.revision_id = b'B-id'
        new_inv.get_entry(b'a-file-id').revision = b'ghost-id'
        new_rev = _mod_revision.Revision(
            b'B-id',
            timestamp=time.time(),
            timezone=0,
            message='Committing against a ghost',
            committer='Joe Foo <*****@*****.**>',
            properties={},
            parent_ids=(b'A-id', b'ghost-id'),
        )
        b.lock_write()
        self.addCleanup(b.unlock)
        b.repository.start_write_group()
        b.repository.add_revision(b'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({b'a-file-id': {b'ghost-id'}},
                         repo.fileids_altered_by_revision_ids([b'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(None,
                               [('add', ('', b'root-id', 'directory', None)),
                                ('add',
                                 ('file', b'file-id', 'file', b'contents\n'))],
                               revision_id=b'A-id')
        builder.build_snapshot([b'A-id'],
                               [('modify', ('file', b'new-content\n'))],
                               revision_id=b'B-id')
        builder.build_snapshot([b'B-id'],
                               [('modify', ('file', b'yet more content\n'))],
                               revision_id=b'C-id')
        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'B-id')
        stacked = self.make_branch('stacked')
        stacked.set_stacked_on_url('../base')
        stacked.pull(source_b, stop_revision=b'C-id')

        stacked.lock_read()
        self.addCleanup(stacked.unlock)
        repo = stacked.repository
        keys = {b'file-id': {b'A-id'}}
        if stacked.repository.supports_rich_root():
            keys[b'root-id'] = {b'A-id'}
        self.assertEqual(keys, repo.fileids_altered_by_revision_ids([b'A-id']))
Ejemplo n.º 16
0
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'], [
            b'a-file-id-2006-01-01-abcd', b'b-file-id-2006-01-01-defg',
            b'c-funky<file-id>quiji\'"%bo'
        ])
        try:
            main_wt.commit("Commit one", rev_id=b"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.controldir
        branch2_branch = branch2_bzrdir.open_branch()
        set_executability(branch2_wt, 'b', True)
        branch2_wt.commit("branch2, Commit one", rev_id=b"rev-J")

        main_wt.merge_from_branch(branch2_branch)
        set_executability(main_wt, 'b', False)
        main_wt.commit("merge branch1, rev-22", rev_id=b"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)
Ejemplo n.º 17
0
class TestRepository(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def assertFormatAttribute(self, attribute, allowed_values):
        """Assert that the format has an attribute 'attribute'."""
        repo = self.make_repository('repo')
        self.assertSubset([getattr(repo._format, attribute)], allowed_values)

    def test_attribute__fetch_order(self):
        """Test the _fetch_order attribute."""
        self.assertFormatAttribute('_fetch_order',
                                   ('topological', 'unordered'))

    def test_attribute__fetch_uses_deltas(self):
        """Test the _fetch_uses_deltas attribute."""
        self.assertFormatAttribute('_fetch_uses_deltas', (True, False))

    def test_attribute_inventories_store(self):
        """Test the existence of the inventories attribute."""
        tree = self.make_branch_and_tree('tree')
        repo = tree.branch.repository
        self.assertIsInstance(repo.inventories, versionedfile.VersionedFiles)

    def test_attribute_inventories_basics(self):
        """Test basic aspects of the inventories attribute."""
        tree = self.make_branch_and_tree('tree')
        repo = tree.branch.repository
        rev_id = (tree.commit('a'), )
        tree.lock_read()
        self.addCleanup(tree.unlock)
        self.assertEqual({rev_id}, set(repo.inventories.keys()))

    def test_attribute_revision_store(self):
        """Test the existence of the revisions attribute."""
        tree = self.make_branch_and_tree('tree')
        repo = tree.branch.repository
        self.assertIsInstance(repo.revisions, versionedfile.VersionedFiles)

    def test_attribute_revision_store_basics(self):
        """Test the basic behaviour of the revisions attribute."""
        tree = self.make_branch_and_tree('tree')
        repo = tree.branch.repository
        repo.lock_write()
        try:
            self.assertEqual(set(), set(repo.revisions.keys()))
            revid = (tree.commit("foo"), )
            self.assertEqual({revid}, set(repo.revisions.keys()))
            self.assertEqual({revid: ()},
                             repo.revisions.get_parent_map([revid]))
        finally:
            repo.unlock()
        tree2 = self.make_branch_and_tree('tree2')
        tree2.pull(tree.branch)
        left_id = (tree2.commit('left'), )
        right_id = (tree.commit('right'), )
        tree.merge_from_branch(tree2.branch)
        merge_id = (tree.commit('merged'), )
        repo.lock_read()
        self.addCleanup(repo.unlock)
        self.assertEqual({revid, left_id, right_id, merge_id},
                         set(repo.revisions.keys()))
        self.assertEqual(
            {
                revid: (),
                left_id: (revid, ),
                right_id: (revid, ),
                merge_id: (right_id, left_id)
            }, repo.revisions.get_parent_map(repo.revisions.keys()))

    def test_attribute_signature_store(self):
        """Test the existence of the signatures attribute."""
        tree = self.make_branch_and_tree('tree')
        repo = tree.branch.repository
        self.assertIsInstance(repo.signatures, versionedfile.VersionedFiles)

    def test_exposed_versioned_files_are_marked_dirty(self):
        repo = self.make_repository('.')
        repo.lock_write()
        signatures = repo.signatures
        revisions = repo.revisions
        inventories = repo.inventories
        repo.unlock()
        self.assertRaises(errors.ObjectNotLocked, signatures.keys)
        self.assertRaises(errors.ObjectNotLocked, revisions.keys)
        self.assertRaises(errors.ObjectNotLocked, inventories.keys)
        self.assertRaises(errors.ObjectNotLocked, signatures.add_lines,
                          ('foo', ), [], [])
        self.assertRaises(errors.ObjectNotLocked, revisions.add_lines,
                          ('foo', ), [], [])
        self.assertRaises(errors.ObjectNotLocked, inventories.add_lines,
                          ('foo', ), [], [])

    def test__get_sink(self):
        repo = self.make_repository('repo')
        sink = repo._get_sink()
        self.assertIsInstance(sink, vf_repository.StreamSink)

    def test_get_serializer_format(self):
        repo = self.make_repository('.')
        format = repo.get_serializer_format()
        self.assertEqual(repo._serializer.format_num, format)

    def test_add_revision_inventory_sha1(self):
        inv = inventory.Inventory(revision_id=b'A')
        inv.root.revision = b'A'
        inv.root.file_id = b'fixed-root'
        # Insert the inventory on its own to an identical repository, to get
        # its sha1.
        reference_repo = self.make_repository('reference_repo')
        reference_repo.lock_write()
        reference_repo.start_write_group()
        inv_sha1 = reference_repo.add_inventory(b'A', inv, [])
        reference_repo.abort_write_group()
        reference_repo.unlock()
        # Now insert a revision with this inventory, and it should get the same
        # sha1.
        repo = self.make_repository('repo')
        repo.lock_write()
        repo.start_write_group()
        root_id = inv.root.file_id
        repo.texts.add_lines((b'fixed-root', b'A'), [], [])
        repo.add_revision(b'A',
                          _mod_revision.Revision(b'A',
                                                 committer='B',
                                                 timestamp=0,
                                                 timezone=0,
                                                 message='C'),
                          inv=inv)
        repo.commit_write_group()
        repo.unlock()
        repo.lock_read()
        self.assertEqual(inv_sha1, repo.get_revision(b'A').inventory_sha1)
        repo.unlock()

    def test_install_revisions(self):
        wt = self.make_branch_and_tree('source')
        wt.commit('A', allow_pointless=True, rev_id=b'A')
        repo = wt.branch.repository
        repo.lock_write()
        repo.start_write_group()
        repo.sign_revision(b'A', gpg.LoopbackGPGStrategy(None))
        repo.commit_write_group()
        repo.unlock()
        repo.lock_read()
        self.addCleanup(repo.unlock)
        repo2 = self.make_repository('repo2')
        revision = repo.get_revision(b'A')
        tree = repo.revision_tree(b'A')
        signature = repo.get_signature_text(b'A')
        repo2.lock_write()
        self.addCleanup(repo2.unlock)
        vf_repository.install_revisions(repo2, [(revision, tree, signature)])
        self.assertEqual(revision, repo2.get_revision(b'A'))
        self.assertEqual(signature, repo2.get_signature_text(b'A'))

    def test_attribute_text_store(self):
        """Test the existence of the texts attribute."""
        tree = self.make_branch_and_tree('tree')
        repo = tree.branch.repository
        self.assertIsInstance(repo.texts, versionedfile.VersionedFiles)

    def test_iter_inventories_is_ordered(self):
        # just a smoke test
        tree = self.make_branch_and_tree('a')
        first_revision = tree.commit('')
        second_revision = tree.commit('')
        tree.lock_read()
        self.addCleanup(tree.unlock)
        revs = (first_revision, second_revision)
        invs = tree.branch.repository.iter_inventories(revs)
        for rev_id, inv in zip(revs, invs):
            self.assertEqual(rev_id, inv.revision_id)
            self.assertIsInstance(inv, inventory.CommonInventory)

    def test_item_keys_introduced_by(self):
        # Make a repo with one revision and one versioned file.
        tree = self.make_branch_and_tree('t')
        self.build_tree(['t/foo'])
        tree.add('foo', b'file1')
        tree.commit('message', rev_id=b'rev_id')
        repo = tree.branch.repository
        repo.lock_write()
        repo.start_write_group()
        try:
            repo.sign_revision(b'rev_id', gpg.LoopbackGPGStrategy(None))
        except errors.UnsupportedOperation:
            signature_texts = []
        else:
            signature_texts = [b'rev_id']
        repo.commit_write_group()
        repo.unlock()
        repo.lock_read()
        self.addCleanup(repo.unlock)

        # Item keys will be in this order, for maximum convenience for
        # generating data to insert into knit repository:
        #   * files
        #   * inventory
        #   * signatures
        #   * revisions
        expected_item_keys = [('file', b'file1', [b'rev_id']),
                              ('inventory', None, [b'rev_id']),
                              ('signatures', None, signature_texts),
                              ('revisions', None, [b'rev_id'])]
        item_keys = list(repo.item_keys_introduced_by([b'rev_id']))
        item_keys = [(kind, file_id, list(versions))
                     for (kind, file_id, versions) in item_keys]

        if repo.supports_rich_root():
            # Check for the root versioned file in the item_keys, then remove
            # it from streamed_names so we can compare that with
            # expected_record_names.
            # Note that the file keys can be in any order, so this test is
            # written to allow that.
            inv = repo.get_inventory(b'rev_id')
            root_item_key = ('file', inv.root.file_id, [b'rev_id'])
            self.assertIn(root_item_key, item_keys)
            item_keys.remove(root_item_key)

        self.assertEqual(expected_item_keys, item_keys)

    def test_attribute_text_store_basics(self):
        """Test the basic behaviour of the text store."""
        tree = self.make_branch_and_tree('tree')
        repo = tree.branch.repository
        file_id = b"Foo:Bar"
        file_key = (file_id, )
        with tree.lock_write():
            self.assertEqual(set(), set(repo.texts.keys()))
            tree.add(['foo'], [file_id], ['file'])
            tree.put_file_bytes_non_atomic('foo', b'content\n')
            try:
                rev_key = (tree.commit("foo"), )
            except errors.IllegalPath:
                raise tests.TestNotApplicable(
                    'file_id %r cannot be stored on this'
                    ' platform for this repo format' % (file_id, ))
            if repo._format.rich_root_data:
                root_commit = (tree.get_root_id(), ) + rev_key
                keys = {root_commit}
                parents = {root_commit: ()}
            else:
                keys = set()
                parents = {}
            keys.add(file_key + rev_key)
            parents[file_key + rev_key] = ()
            self.assertEqual(keys, set(repo.texts.keys()))
            self.assertEqual(parents,
                             repo.texts.get_parent_map(repo.texts.keys()))
        tree2 = self.make_branch_and_tree('tree2')
        tree2.pull(tree.branch)
        tree2.put_file_bytes_non_atomic('foo', b'right\n')
        right_key = (tree2.commit('right'), )
        keys.add(file_key + right_key)
        parents[file_key + right_key] = (file_key + rev_key, )
        tree.put_file_bytes_non_atomic('foo', b'left\n')
        left_key = (tree.commit('left'), )
        keys.add(file_key + left_key)
        parents[file_key + left_key] = (file_key + rev_key, )
        tree.merge_from_branch(tree2.branch)
        tree.put_file_bytes_non_atomic('foo', b'merged\n')
        try:
            tree.auto_resolve()
        except errors.UnsupportedOperation:
            pass
        merge_key = (tree.commit('merged'), )
        keys.add(file_key + merge_key)
        parents[file_key + merge_key] = (file_key + left_key,
                                         file_key + right_key)
        repo.lock_read()
        self.addCleanup(repo.unlock)
        self.assertEqual(keys, set(repo.texts.keys()))
        self.assertEqual(parents, repo.texts.get_parent_map(repo.texts.keys()))
Ejemplo n.º 18
0
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'], [
            b'a-file-id-2006-01-01-abcd', b'b-file-id-2006-01-01-defg',
            b'c-funky<file-id>quiji%bo'
        ])
        try:
            main_wt.commit("Commit one", rev_id=b"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'], [b'file-d'])
        bt1.commit("branch1, Commit one", rev_id=b"rev-E")

        # -------- end E -----------

        self.touch(main_wt, "a")
        main_wt.commit("Commit two", rev_id=b"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=b"rev-J")

        # -------- end J -----------

        main_wt.merge_from_branch(b1)
        main_wt.commit("merge branch1, rev-11", rev_id=b"rev-C")

        # -------- end C -----------

        bt1.rename_one("d", "e")
        bt1.commit("branch1, commit two", rev_id=b"rev-F")

        # -------- end F -----------

        self.touch(bt2, "c")
        bt2.commit("branch2, commit two", rev_id=b"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=b"rev-<D>")

        # end D

        main_wt.merge_from_branch(branch2_branch)
        main_wt.commit("merge branch1, rev-22", rev_id=b"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(
            [b"rev-J", b"rev-K"])
        self.assertEqual(
            {
                b'b-file-id-2006-01-01-defg': {b'rev-J'},
                b'c-funky<file-id>quiji%bo': {b'rev-K'}
            },
            self.branch.repository.fileids_altered_by_revision_ids(
                [b"rev-J", b"rev-K"]))

        self.assertEqual(
            {
                b'b-file-id-2006-01-01-defg': {b'rev-<D>'},
                b'file-d': {b'rev-F'},
            },
            self.branch.repository.fileids_altered_by_revision_ids(
                [b'rev-<D>', b'rev-F']))

        self.assertEqual(
            {
                b'b-file-id-2006-01-01-defg': {b'rev-<D>', b'rev-G', b'rev-J'},
                b'c-funky<file-id>quiji%bo': {b'rev-K'},
                b'file-d': {b'rev-F'},
            },
            self.branch.repository.fileids_altered_by_revision_ids(
                [b'rev-<D>', b'rev-G', b'rev-F', b'rev-K', b'rev-J']))

        self.assertEqual(
            {
                b'a-file-id-2006-01-01-abcd': {b'rev-B'},
                b'b-file-id-2006-01-01-defg': {b'rev-<D>', b'rev-G', b'rev-J'},
                b'c-funky<file-id>quiji%bo': {b'rev-K'},
                b'file-d': {b'rev-F'},
            },
            self.branch.repository.fileids_altered_by_revision_ids([
                b'rev-G', b'rev-F', b'rev-C', b'rev-B', b'rev-<D>', b'rev-K',
                b'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().path2id('')
        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(
            {
                b'a-file-id-2006-01-01-abcd': {b'rev-A'},
                b'b-file-id-2006-01-01-defg': {b'rev-A'},
                b'c-funky<file-id>quiji%bo': {b'rev-A'},
            }, self.fileids_altered_by_revision_ids([b"rev-A"]))
        self.assertEqual(
            {b'a-file-id-2006-01-01-abcd': {b'rev-B'}},
            self.branch.repository.fileids_altered_by_revision_ids([b"rev-B"]))
        self.assertEqual(
            {b'b-file-id-2006-01-01-defg': {b'rev-<D>'}},
            self.branch.repository.fileids_altered_by_revision_ids(
                [b"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.assertEqual(l1, l2)
Ejemplo n.º 19
0
class TestResumeableWriteGroup(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def make_write_locked_repo(self, relpath='repo'):
        repo = self.make_repository(relpath)
        repo.lock_write()
        self.addCleanup(repo.unlock)
        return repo

    def reopen_repo(self, repo):
        same_repo = repo.controldir.open_repository()
        same_repo.lock_write()
        self.addCleanup(same_repo.unlock)
        return same_repo

    def require_suspendable_write_groups(self, reason):
        repo = self.make_repository('__suspend_test')
        repo.lock_write()
        self.addCleanup(repo.unlock)
        repo.start_write_group()
        try:
            wg_tokens = repo.suspend_write_group()
        except errors.UnsuspendableWriteGroup:
            repo.abort_write_group()
            raise tests.TestNotApplicable(reason)

    def test_suspend_write_group(self):
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        repo.texts.add_lines((b'file-id', b'revid'), (), [b'lines'])
        try:
            wg_tokens = repo.suspend_write_group()
        except errors.UnsuspendableWriteGroup:
            # The contract for repos that don't support suspending write groups
            # is that suspend_write_group raises UnsuspendableWriteGroup, but
            # is otherwise a no-op.  So we can still e.g. abort the write group
            # as usual.
            self.assertTrue(repo.is_in_write_group())
            repo.abort_write_group()
        else:
            # After suspending a write group we are no longer in a write group
            self.assertFalse(repo.is_in_write_group())
            # suspend_write_group returns a list of tokens, which are strs.  If
            # no other write groups were resumed, there will only be one token.
            self.assertEqual(1, len(wg_tokens))
            self.assertIsInstance(wg_tokens[0], str)
            # See also test_pack_repository's test of the same name.

    def test_resume_write_group_then_abort(self):
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        text_key = (b'file-id', b'revid')
        repo.texts.add_lines(text_key, (), [b'lines'])
        try:
            wg_tokens = repo.suspend_write_group()
        except errors.UnsuspendableWriteGroup:
            # If the repo does not support suspending write groups, it doesn't
            # support resuming them either.
            repo.abort_write_group()
            self.assertRaises(
                errors.UnsuspendableWriteGroup, repo.resume_write_group, [])
        else:
            #self.assertEqual([], list(repo.texts.keys()))
            same_repo = self.reopen_repo(repo)
            same_repo.resume_write_group(wg_tokens)
            self.assertEqual([text_key], list(same_repo.texts.keys()))
            self.assertTrue(same_repo.is_in_write_group())
            same_repo.abort_write_group()
            self.assertEqual([], list(repo.texts.keys()))
            # See also test_pack_repository's test of the same name.

    def test_multiple_resume_write_group(self):
        self.require_suspendable_write_groups(
            'Cannot test resume on repo that does not support suspending')
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        first_key = (b'file-id', b'revid')
        repo.texts.add_lines(first_key, (), [b'lines'])
        wg_tokens = repo.suspend_write_group()
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        self.assertTrue(same_repo.is_in_write_group())
        second_key = (b'file-id', b'second-revid')
        same_repo.texts.add_lines(second_key, (first_key,), [b'more lines'])
        try:
            new_wg_tokens = same_repo.suspend_write_group()
        except:
            same_repo.abort_write_group(suppress_errors=True)
            raise
        self.assertEqual(2, len(new_wg_tokens))
        self.assertSubset(wg_tokens, new_wg_tokens)
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(new_wg_tokens)
        both_keys = {first_key, second_key}
        self.assertEqual(both_keys, same_repo.texts.keys())
        same_repo.abort_write_group()

    def test_no_op_suspend_resume(self):
        self.require_suspendable_write_groups(
            'Cannot test resume on repo that does not support suspending')
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        text_key = (b'file-id', b'revid')
        repo.texts.add_lines(text_key, (), [b'lines'])
        wg_tokens = repo.suspend_write_group()
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        new_wg_tokens = same_repo.suspend_write_group()
        self.assertEqual(wg_tokens, new_wg_tokens)
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        self.assertEqual([text_key], list(same_repo.texts.keys()))
        same_repo.abort_write_group()

    def test_read_after_suspend_fails(self):
        self.require_suspendable_write_groups(
            'Cannot test suspend on repo that does not support suspending')
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        text_key = (b'file-id', b'revid')
        repo.texts.add_lines(text_key, (), [b'lines'])
        wg_tokens = repo.suspend_write_group()
        self.assertEqual([], list(repo.texts.keys()))

    def test_read_after_second_suspend_fails(self):
        self.require_suspendable_write_groups(
            'Cannot test suspend on repo that does not support suspending')
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        text_key = (b'file-id', b'revid')
        repo.texts.add_lines(text_key, (), [b'lines'])
        wg_tokens = repo.suspend_write_group()
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        same_repo.suspend_write_group()
        self.assertEqual([], list(same_repo.texts.keys()))

    def test_read_after_resume_abort_fails(self):
        self.require_suspendable_write_groups(
            'Cannot test suspend on repo that does not support suspending')
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        text_key = (b'file-id', b'revid')
        repo.texts.add_lines(text_key, (), [b'lines'])
        wg_tokens = repo.suspend_write_group()
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        same_repo.abort_write_group()
        self.assertEqual([], list(same_repo.texts.keys()))

    def test_cannot_resume_aborted_write_group(self):
        self.require_suspendable_write_groups(
            'Cannot test resume on repo that does not support suspending')
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        text_key = (b'file-id', b'revid')
        repo.texts.add_lines(text_key, (), [b'lines'])
        wg_tokens = repo.suspend_write_group()
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        same_repo.abort_write_group()
        same_repo = self.reopen_repo(repo)
        self.assertRaises(
            errors.UnresumableWriteGroup, same_repo.resume_write_group,
            wg_tokens)

    def test_commit_resumed_write_group_no_new_data(self):
        self.require_suspendable_write_groups(
            'Cannot test resume on repo that does not support suspending')
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        text_key = (b'file-id', b'revid')
        repo.texts.add_lines(text_key, (), [b'lines'])
        wg_tokens = repo.suspend_write_group()
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        same_repo.commit_write_group()
        self.assertEqual([text_key], list(same_repo.texts.keys()))
        self.assertEqual(
            b'lines', next(same_repo.texts.get_record_stream([text_key],
                                                             'unordered', True)).get_bytes_as('fulltext'))
        self.assertRaises(
            errors.UnresumableWriteGroup, same_repo.resume_write_group,
            wg_tokens)

    def test_commit_resumed_write_group_plus_new_data(self):
        self.require_suspendable_write_groups(
            'Cannot test resume on repo that does not support suspending')
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        first_key = (b'file-id', b'revid')
        repo.texts.add_lines(first_key, (), [b'lines'])
        wg_tokens = repo.suspend_write_group()
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        second_key = (b'file-id', b'second-revid')
        same_repo.texts.add_lines(second_key, (first_key,), [b'more lines'])
        same_repo.commit_write_group()
        self.assertEqual(
            {first_key, second_key}, set(same_repo.texts.keys()))
        self.assertEqual(
            b'lines', next(same_repo.texts.get_record_stream([first_key],
                                                             'unordered', True)).get_bytes_as('fulltext'))
        self.assertEqual(
            b'more lines', next(same_repo.texts.get_record_stream([second_key],
                                                                  'unordered', True)).get_bytes_as('fulltext'))

    def make_source_with_delta_record(self):
        # Make a source repository with a delta record in it.
        source_repo = self.make_write_locked_repo('source')
        source_repo.start_write_group()
        key_base = (b'file-id', b'base')
        key_delta = (b'file-id', b'delta')

        def text_stream():
            yield versionedfile.FulltextContentFactory(
                key_base, (), None, b'lines\n')
            yield versionedfile.FulltextContentFactory(
                key_delta, (key_base,), None, b'more\nlines\n')
        source_repo.texts.insert_record_stream(text_stream())
        source_repo.commit_write_group()
        return source_repo

    def test_commit_resumed_write_group_with_missing_parents(self):
        self.require_suspendable_write_groups(
            'Cannot test resume on repo that does not support suspending')
        source_repo = self.make_source_with_delta_record()
        key_base = (b'file-id', b'base')
        key_delta = (b'file-id', b'delta')
        # Start a write group, insert just a delta.
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        stream = source_repo.texts.get_record_stream(
            [key_delta], 'unordered', False)
        repo.texts.insert_record_stream(stream)
        # It's either not commitable due to the missing compression parent, or
        # the stacked location has already filled in the fulltext.
        try:
            repo.commit_write_group()
        except errors.BzrCheckError:
            # It refused to commit because we have a missing parent
            pass
        else:
            same_repo = self.reopen_repo(repo)
            same_repo.lock_read()
            record = next(same_repo.texts.get_record_stream([key_delta],
                                                            'unordered', True))
            self.assertEqual(b'more\nlines\n', record.get_bytes_as('fulltext'))
            return
        # Merely suspending and resuming doesn't make it commitable either.
        wg_tokens = repo.suspend_write_group()
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        self.assertRaises(
            errors.BzrCheckError, same_repo.commit_write_group)
        same_repo.abort_write_group()

    def test_commit_resumed_write_group_adding_missing_parents(self):
        self.require_suspendable_write_groups(
            'Cannot test resume on repo that does not support suspending')
        source_repo = self.make_source_with_delta_record()
        key_base = (b'file-id', b'base')
        key_delta = (b'file-id', b'delta')
        # Start a write group.
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        # Add some content so this isn't an empty write group (which may return
        # 0 tokens)
        text_key = (b'file-id', b'revid')
        repo.texts.add_lines(text_key, (), [b'lines'])
        # Suspend it, then resume it.
        wg_tokens = repo.suspend_write_group()
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        # Add a record with a missing compression parent
        stream = source_repo.texts.get_record_stream(
            [key_delta], 'unordered', False)
        same_repo.texts.insert_record_stream(stream)
        # Just like if we'd added that record without a suspend/resume cycle,
        # commit_write_group fails.
        try:
            same_repo.commit_write_group()
        except errors.BzrCheckError:
            pass
        else:
            # If the commit_write_group didn't fail, that is because the
            # insert_record_stream already gave it a fulltext.
            same_repo = self.reopen_repo(repo)
            same_repo.lock_read()
            record = next(same_repo.texts.get_record_stream([key_delta],
                                                            'unordered', True))
            self.assertEqual(b'more\nlines\n', record.get_bytes_as('fulltext'))
            return
        same_repo.abort_write_group()

    def test_add_missing_parent_after_resume(self):
        self.require_suspendable_write_groups(
            'Cannot test resume on repo that does not support suspending')
        source_repo = self.make_source_with_delta_record()
        key_base = (b'file-id', b'base')
        key_delta = (b'file-id', b'delta')
        # Start a write group, insert just a delta.
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        stream = source_repo.texts.get_record_stream(
            [key_delta], 'unordered', False)
        repo.texts.insert_record_stream(stream)
        # Suspend it, then resume it.
        wg_tokens = repo.suspend_write_group()
        same_repo = self.reopen_repo(repo)
        same_repo.resume_write_group(wg_tokens)
        # Fill in the missing compression parent.
        stream = source_repo.texts.get_record_stream(
            [key_base], 'unordered', False)
        same_repo.texts.insert_record_stream(stream)
        same_repo.commit_write_group()

    def test_suspend_empty_initial_write_group(self):
        """Suspending a write group with no writes returns an empty token
        list.
        """
        self.require_suspendable_write_groups(
            'Cannot test suspend on repo that does not support suspending')
        repo = self.make_write_locked_repo()
        repo.start_write_group()
        wg_tokens = repo.suspend_write_group()
        self.assertEqual([], wg_tokens)

    def test_resume_empty_initial_write_group(self):
        """Resuming an empty token list is equivalent to start_write_group."""
        self.require_suspendable_write_groups(
            'Cannot test resume on repo that does not support suspending')
        repo = self.make_write_locked_repo()
        repo.resume_write_group([])
        repo.abort_write_group()
Ejemplo n.º 20
0
class TestCaseWithCorruptRepository(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def setUp(self):
        super(TestCaseWithCorruptRepository, self).setUp()
        # a inventory with no parents and the revision has parents..
        # i.e. a ghost.
        repo = self.make_repository('inventory_with_unnecessary_ghost')
        repo.lock_write()
        repo.start_write_group()
        inv = inventory.Inventory(revision_id=b'ghost')
        inv.root.revision = b'ghost'
        if repo.supports_rich_root():
            root_id = inv.root.file_id
            repo.texts.add_lines((root_id, b'ghost'), [], [])
        sha1 = repo.add_inventory(b'ghost', inv, [])
        rev = _mod_revision.Revision(timestamp=0,
                                     timezone=None,
                                     committer="Foo Bar <*****@*****.**>",
                                     message="Message",
                                     inventory_sha1=sha1,
                                     revision_id=b'ghost')
        rev.parent_ids = [b'the_ghost']
        try:
            repo.add_revision(b'ghost', rev)
        except (errors.NoSuchRevision, errors.RevisionNotPresent):
            raise tests.TestNotApplicable(
                "Cannot test with ghosts for this format.")

        inv = inventory.Inventory(revision_id=b'the_ghost')
        inv.root.revision = b'the_ghost'
        if repo.supports_rich_root():
            root_id = inv.root.file_id
            repo.texts.add_lines((root_id, b'the_ghost'), [], [])
        sha1 = repo.add_inventory(b'the_ghost', inv, [])
        rev = _mod_revision.Revision(timestamp=0,
                                     timezone=None,
                                     committer="Foo Bar <*****@*****.**>",
                                     message="Message",
                                     inventory_sha1=sha1,
                                     revision_id=b'the_ghost')
        rev.parent_ids = []
        repo.add_revision(b'the_ghost', rev)
        # check its setup usefully
        inv_weave = repo.inventories
        possible_parents = (None, ((b'ghost', ), ))
        self.assertSubset(
            inv_weave.get_parent_map([(b'ghost', )])[(b'ghost', )],
            possible_parents)
        repo.commit_write_group()
        repo.unlock()

    def test_corrupt_revision_access_asserts_if_reported_wrong(self):
        repo_url = self.get_url('inventory_with_unnecessary_ghost')
        repo = _mod_repository.Repository.open(repo_url)
        m = MatchesAncestry(repo, b'ghost')
        reported_wrong = False
        try:
            if m.match([b'the_ghost', b'ghost']) is not None:
                reported_wrong = True
        except errors.CorruptRepository:
            # caught the bad data:
            return
        if not reported_wrong:
            return
        self.assertRaises(errors.CorruptRepository, repo.get_revision,
                          b'ghost')

    def test_corrupt_revision_get_revision_reconcile(self):
        repo_url = self.get_url('inventory_with_unnecessary_ghost')
        repo = _mod_repository.Repository.open(repo_url)
        repo.get_revision_reconcile(b'ghost')
Ejemplo n.º 21
0
class TestAddInventoryByDelta(TestCaseWithRepository):

    scenarios = all_repository_vf_format_scenarios()

    def _get_repo_in_write_group(self, path='repository'):
        repo = self.make_repository(path)
        repo.lock_write()
        self.addCleanup(repo.unlock)
        repo.start_write_group()
        return repo

    def test_basis_missing_errors(self):
        repo = self._get_repo_in_write_group()
        try:
            self.assertRaises(errors.NoSuchRevision,
                              repo.add_inventory_by_delta, "missing-revision", [],
                              "new-revision", ["missing-revision"])
        finally:
            repo.abort_write_group()

    def test_not_in_write_group_errors(self):
        repo = self.make_repository('repository')
        repo.lock_write()
        self.addCleanup(repo.unlock)
        self.assertRaises(AssertionError, repo.add_inventory_by_delta,
                          "missing-revision", [], "new-revision", ["missing-revision"])

    def make_inv_delta(self, old, new):
        """Make an inventory delta from two inventories."""
        by_id = getattr(old, '_byid', None)
        if by_id is None:
            old_ids = set(entry.file_id for entry in old.iter_just_entries())
        else:
            old_ids = set(by_id)
        by_id = getattr(new, '_byid', None)
        if by_id is None:
            new_ids = set(entry.file_id for entry in new.iter_just_entries())
        else:
            new_ids = set(by_id)

        adds = new_ids - old_ids
        deletes = old_ids - new_ids
        common = old_ids.intersection(new_ids)
        delta = []
        for file_id in deletes:
            delta.append((old.id2path(file_id), None, file_id, None))
        for file_id in adds:
            delta.append((None, new.id2path(file_id),
                          file_id, new.get_entry(file_id)))
        for file_id in common:
            if old.get_entry(file_id) != new.get_entry(file_id):
                delta.append((old.id2path(file_id), new.id2path(file_id),
                              file_id, new[file_id]))
        return delta

    def test_same_validator(self):
        # Adding an inventory via delta or direct results in the same
        # validator.
        tree = self.make_branch_and_tree('tree')
        revid = tree.commit("empty post")
        # tree.basis_tree() always uses a plain Inventory from the dirstate, we
        # want the same format inventory as we have in the repository
        revtree = tree.branch.repository.revision_tree(
            tree.branch.last_revision())
        tree.basis_tree()
        revtree.lock_read()
        self.addCleanup(revtree.unlock)
        old_inv = tree.branch.repository.revision_tree(
            revision.NULL_REVISION).root_inventory
        new_inv = revtree.root_inventory
        delta = self.make_inv_delta(old_inv, new_inv)
        repo_direct = self._get_repo_in_write_group('direct')
        add_validator = repo_direct.add_inventory(revid, new_inv, [])
        repo_direct.commit_write_group()
        repo_delta = self._get_repo_in_write_group('delta')
        try:
            delta_validator, inv = repo_delta.add_inventory_by_delta(
                revision.NULL_REVISION, delta, revid, [])
        except:
            repo_delta.abort_write_group()
            raise
        else:
            repo_delta.commit_write_group()
        self.assertEqual(add_validator, delta_validator)
        self.assertEqual(list(new_inv.iter_entries()),
                         list(inv.iter_entries()))