← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:split-revisionstatus into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:split-revisionstatus into launchpad:master.

Commit message:
Split RevisionStatus{Report,Artifact} into separate modules

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/414176

While they're associated with `GitRepository`, they don't really belong in the same module, and splitting them may reduce circular import problems elsewhere in future.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:split-revisionstatus into launchpad:master.
diff --git a/lib/lp/_schema_circular_imports.py b/lib/lp/_schema_circular_imports.py
index af12d9a..2ddb85c 100644
--- a/lib/lp/_schema_circular_imports.py
+++ b/lib/lp/_schema_circular_imports.py
@@ -64,10 +64,7 @@ from lp.code.interfaces.codereviewcomment import ICodeReviewComment
 from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
 from lp.code.interfaces.diff import IPreviewDiff
 from lp.code.interfaces.gitref import IGitRef
-from lp.code.interfaces.gitrepository import (
-    IGitRepository,
-    IRevisionStatusReport,
-    )
+from lp.code.interfaces.gitrepository import IGitRepository
 from lp.code.interfaces.gitrule import (
     IGitNascentRule,
     IGitNascentRuleGrant,
@@ -80,6 +77,7 @@ from lp.code.interfaces.hasbranches import (
     IHasRequestedReviews,
     )
 from lp.code.interfaces.hasrecipes import IHasRecipes
+from lp.code.interfaces.revisionstatus import IRevisionStatusReport
 from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
 from lp.code.interfaces.sourcepackagerecipebuild import (
     ISourcePackageRecipeBuild,
diff --git a/lib/lp/code/browser/configure.zcml b/lib/lp/code/browser/configure.zcml
index 31b3938..33d0368 100644
--- a/lib/lp/code/browser/configure.zcml
+++ b/lib/lp/code/browser/configure.zcml
@@ -964,7 +964,7 @@
         permission="zope.Public"/>
 
   <browser:url
-        for="lp.code.interfaces.gitrepository.IRevisionStatusReport"
+        for="lp.code.interfaces.revisionstatus.IRevisionStatusReport"
         path_expression="string:+status/${id}"
         attribute_to_parent="git_repository"
         rootsite="code"/>
diff --git a/lib/lp/code/browser/gitrepository.py b/lib/lp/code/browser/gitrepository.py
index cd35d5d..b8d7b3e 100644
--- a/lib/lp/code/browser/gitrepository.py
+++ b/lib/lp/code/browser/gitrepository.py
@@ -105,8 +105,8 @@ from lp.code.interfaces.gitrepository import (
     ContributorGitIdentity,
     IGitRepository,
     IGitRepositorySet,
-    IRevisionStatusReportSet,
     )
+from lp.code.interfaces.revisionstatus import IRevisionStatusReportSet
 from lp.code.vocabularies.gitrule import GitPermissionsVocabulary
 from lp.registry.interfaces.person import (
     IPerson,
diff --git a/lib/lp/code/configure.zcml b/lib/lp/code/configure.zcml
index b5399af..8ba4643 100644
--- a/lib/lp/code/configure.zcml
+++ b/lib/lp/code/configure.zcml
@@ -955,36 +955,36 @@
 
   <!-- RevisionStatusReport -->
 
-  <class class="lp.code.model.gitrepository.RevisionStatusReport">
+  <class class="lp.code.model.revisionstatus.RevisionStatusReport">
     <require
         permission="launchpad.View"
-        interface="lp.code.interfaces.gitrepository.IRevisionStatusReportView
-                   lp.code.interfaces.gitrepository.IRevisionStatusReportEditableAttributes" />
+        interface="lp.code.interfaces.revisionstatus.IRevisionStatusReportView
+                   lp.code.interfaces.revisionstatus.IRevisionStatusReportEditableAttributes" />
     <require
         permission="launchpad.Edit"
-        interface="lp.code.interfaces.gitrepository.IRevisionStatusReportEdit"
-        set_schema="lp.code.interfaces.gitrepository.IRevisionStatusReportEditableAttributes" />
+        interface="lp.code.interfaces.revisionstatus.IRevisionStatusReportEdit"
+        set_schema="lp.code.interfaces.revisionstatus.IRevisionStatusReportEditableAttributes" />
   </class>
-  <class class="lp.code.model.gitrepository.RevisionStatusArtifact">
+  <class class="lp.code.model.revisionstatus.RevisionStatusArtifact">
     <require
         permission="launchpad.View"
-        interface="lp.code.interfaces.gitrepository.IRevisionStatusArtifact" />
+        interface="lp.code.interfaces.revisionstatus.IRevisionStatusArtifact" />
   </class>
-  <class class="lp.code.model.gitrepository.RevisionStatusReportSet">
-    <allow interface="lp.code.interfaces.gitrepository.IRevisionStatusReportSet" />
+  <class class="lp.code.model.revisionstatus.RevisionStatusReportSet">
+    <allow interface="lp.code.interfaces.revisionstatus.IRevisionStatusReportSet" />
   </class>
   <securedutility
-      class="lp.code.model.gitrepository.RevisionStatusReportSet"
-      provides="lp.code.interfaces.gitrepository.IRevisionStatusReportSet">
-     <allow interface="lp.code.interfaces.gitrepository.IRevisionStatusReportSet" />
+      class="lp.code.model.revisionstatus.RevisionStatusReportSet"
+      provides="lp.code.interfaces.revisionstatus.IRevisionStatusReportSet">
+     <allow interface="lp.code.interfaces.revisionstatus.IRevisionStatusReportSet" />
   </securedutility>
-  <class class="lp.code.model.gitrepository.RevisionStatusArtifactSet">
-    <allow interface="lp.code.interfaces.gitrepository.IRevisionStatusArtifactSet" />
+  <class class="lp.code.model.revisionstatus.RevisionStatusArtifactSet">
+    <allow interface="lp.code.interfaces.revisionstatus.IRevisionStatusArtifactSet" />
   </class>
   <securedutility
-      class="lp.code.model.gitrepository.RevisionStatusArtifactSet"
-      provides="lp.code.interfaces.gitrepository.IRevisionStatusArtifactSet">
-     <allow interface="lp.code.interfaces.gitrepository.IRevisionStatusArtifactSet" />
+      class="lp.code.model.revisionstatus.RevisionStatusArtifactSet"
+      provides="lp.code.interfaces.revisionstatus.IRevisionStatusArtifactSet">
+     <allow interface="lp.code.interfaces.revisionstatus.IRevisionStatusArtifactSet" />
   </securedutility>
 
   <!-- Git repository access rules -->
diff --git a/lib/lp/code/interfaces/gitrepository.py b/lib/lp/code/interfaces/gitrepository.py
index 888136b..ef25343 100644
--- a/lib/lp/code/interfaces/gitrepository.py
+++ b/lib/lp/code/interfaces/gitrepository.py
@@ -13,15 +13,9 @@ __all__ = [
     'IGitRepositoryExpensiveRequest',
     'IGitRepositorySet',
     'IHasGitRepositoryURL',
-    'IRevisionStatusArtifact',
-    'IRevisionStatusArtifactSet',
-    'IRevisionStatusReport',
-    'IRevisionStatusReportSet',
-    'RevisionStatusReportsFeatureDisabled',
     'user_has_special_git_repository_access',
     ]
 
-import http.client
 import re
 from textwrap import dedent
 
@@ -29,7 +23,6 @@ from lazr.lifecycle.snapshot import doNotSnapshot
 from lazr.restful.declarations import (
     call_with,
     collection_default_content,
-    error_status,
     export_destructor_operation,
     export_factory_operation,
     export_operation_as,
@@ -58,7 +51,6 @@ from zope.interface import (
     )
 from zope.schema import (
     Bool,
-    Bytes,
     Choice,
     Datetime,
     Int,
@@ -66,12 +58,10 @@ from zope.schema import (
     Text,
     TextLine,
     )
-from zope.security.interfaces import Unauthorized
 
 from lp import _
 from lp.app.enums import InformationType
 from lp.app.validators import LaunchpadValidationError
-from lp.app.validators.attachment import attachment_size_constraint
 from lp.code.enums import (
     BranchMergeProposalStatus,
     BranchSubscriptionDiffSize,
@@ -80,12 +70,11 @@ from lp.code.enums import (
     GitListingSort,
     GitRepositoryStatus,
     GitRepositoryType,
-    RevisionStatusArtifactType,
-    RevisionStatusResult,
     )
 from lp.code.interfaces.defaultgit import ICanHasDefaultGitRepository
 from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
 from lp.code.interfaces.hasrecipes import IHasRecipes
+from lp.code.interfaces.revisionstatus import IRevisionStatusReport
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
     )
@@ -104,7 +93,6 @@ from lp.services.fields import (
     InlineObject,
     PersonChoice,
     PublicPersonChoice,
-    URIField,
     )
 from lp.services.webhooks.interfaces import IWebhookTarget
 
@@ -827,180 +815,6 @@ class IGitRepositoryExpensiveRequest(Interface):
         that is not an admin or a registry expert."""
 
 
-@error_status(http.client.UNAUTHORIZED)
-class RevisionStatusReportsFeatureDisabled(Unauthorized):
-    """Only certain users can access APIs for revision status reports."""
-
-    def __init__(self):
-        super().__init__(
-            "You do not have permission to create revision status reports")
-
-
-class IRevisionStatusReportView(Interface):
-    """`IRevisionStatusReport` attributes that require launchpad.View."""
-
-    id = Int(title=_("ID"), required=True, readonly=True)
-
-    date_created = exported(Datetime(
-        title=_("When the report was created."), required=True, readonly=True))
-    date_started = exported(Datetime(
-        title=_("When the report was started.")), readonly=False)
-    date_finished = exported(Datetime(
-        title=_("When the report has finished.")), readonly=False)
-
-
-class IRevisionStatusReportEditableAttributes(Interface):
-    """`IRevisionStatusReport` attributes that can be edited.
-
-    These attributes need launchpad.View to see, and launchpad.Edit to change.
-    """
-
-    title = exported(TextLine(
-        title=_("A short title for the report."), required=True,
-        readonly=False))
-
-    git_repository = exported(Reference(
-        title=_("The Git repository for which this report is built."),
-        # Really IGitRepository, patched in _schema_circular_imports.py.
-        schema=Interface, required=True, readonly=True))
-
-    commit_sha1 = exported(TextLine(
-        title=_("The Git commit for which this report is built."),
-        required=True, readonly=True))
-
-    url = exported(URIField(title=_("URL"), required=False, readonly=False,
-                            description=_("The external url of the report.")))
-
-    result_summary = exported(TextLine(
-        title=_("A short summary of the result."), required=False,
-        readonly=False))
-
-    result = exported(Choice(
-        title=_('Result of the report'),  readonly=True,
-        required=False, vocabulary=RevisionStatusResult))
-
-    @mutator_for(result)
-    @operation_parameters(result=copy_field(result))
-    @export_write_operation()
-    @operation_for_version("devel")
-    def transitionToNewResult(result):
-        """Set the RevisionStatusReport result.
-
-        Set the revision status report result."""
-
-
-class IRevisionStatusReportEdit(Interface):
-    """`IRevisionStatusReport` attributes that require launchpad.Edit."""
-
-    @operation_parameters(
-        log_data=Bytes(title=_("The content of the artifact in bytes."),
-                       constraint=attachment_size_constraint))
-    @scoped(AccessTokenScope.REPOSITORY_BUILD_STATUS.title)
-    @export_write_operation()
-    @export_operation_as(name="setLog")
-    @operation_for_version("devel")
-    def api_setLog(log_data):
-        """Set a new log on an existing status report.
-
-        :param log_data: The contents (in bytes) of the log.
-        """
-
-    @operation_parameters(
-        title=TextLine(title=_("A short title for the report."),
-                       required=False),
-        url=TextLine(title=_("The external link of the status report."),
-                     required=False),
-        result_summary=TextLine(title=_("A short summary of the result."),
-                                required=False),
-        result=Choice(vocabulary=RevisionStatusResult, required=False))
-    @scoped(AccessTokenScope.REPOSITORY_BUILD_STATUS.title)
-    @export_write_operation()
-    @operation_for_version("devel")
-    def update(title, url, result_summary, result):
-        """Updates a status report.
-
-        :param title: A short title for the report.
-        :param url: The external url of the report.
-        :param result_summary: A short summary of the result.
-        :param result: The result of the report.
-        """
-
-
-@exported_as_webservice_entry(as_of="beta")
-class IRevisionStatusReport(IRevisionStatusReportView,
-                            IRevisionStatusReportEditableAttributes,
-                            IRevisionStatusReportEdit):
-    """An revision status report for a Git commit."""
-
-
-class IRevisionStatusReportSet(Interface):
-    """The set of all revision status reports."""
-
-    def new(creator, title, git_repository, commit_sha1, date_created=None,
-            url=None, result_summary=None, result=None, date_started=None,
-            date_finished=None, log=None):
-        """Return a new revision status report.
-
-        :param title: A text string.
-        :param git_repository: An `IGitRepository` for which the report
-            is being created.
-        :param commit_sha1: The sha1 of the commit for which the report
-            is being created.
-        :param date_created: The date when the report is being created.
-        :param url: External URL to view result of report.
-        :param result_summary: A short summary of the result.
-        :param result: The result of the check job for this revision.
-        :param date_started: DateTime that report was started.
-        :param date_finished: DateTime that report was completed.
-        :param log: Stores the content of the artifact for this report.
-        """
-
-    def getByID(id):
-        """Returns the RevisionStatusReport for a given ID."""
-
-    def findByRepository(repository):
-        """Returns all `RevisionStatusReport` for a repository."""
-
-    def findByCommit(repository, commit_sha1):
-        """Returns all `RevisionStatusReport` for a repository and commit."""
-
-    def deleteForRepository(repository):
-        """Delete all `RevisionStatusReport` for a repository."""
-
-
-class IRevisionStatusArtifactSet(Interface):
-    """The set of all revision status artifacts."""
-
-    def new(lfa, report):
-        """Return a new revision status artifact.
-
-        :param lfa: An `ILibraryFileAlias`.
-        :param report: An `IRevisionStatusReport` for which the
-            artifact is being created.
-        """
-
-    def getByID(id):
-        """Returns the RevisionStatusArtifact for a given ID."""
-
-    def findByReport(report):
-        """Returns the set of artifacts for a given report."""
-
-
-class IRevisionStatusArtifact(Interface):
-    id = Int(title=_("ID"), required=True, readonly=True)
-
-    report = Attribute(
-        "The `RevisionStatusReport` that this artifact is linked to.")
-
-    library_file = Attribute(
-        "The `LibraryFileAlias` object containing information for "
-        "a revision status report.")
-
-    artifact_type = Choice(
-        title=_('The type of artifact, only log for now.'),
-        vocabulary=RevisionStatusArtifactType)
-
-
 class IGitRepositoryEdit(IWebhookTarget, IAccessTokenTarget):
     """IGitRepository methods that require launchpad.Edit permission."""
 
diff --git a/lib/lp/code/interfaces/revisionstatus.py b/lib/lp/code/interfaces/revisionstatus.py
new file mode 100644
index 0000000..630dec4
--- /dev/null
+++ b/lib/lp/code/interfaces/revisionstatus.py
@@ -0,0 +1,224 @@
+# Copyright 2021-2022 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Interfaces for revision status reports and artifacts."""
+
+__all__ = [
+    'IRevisionStatusArtifact',
+    'IRevisionStatusArtifactSet',
+    'IRevisionStatusReport',
+    'IRevisionStatusReportSet',
+    'RevisionStatusReportsFeatureDisabled',
+    ]
+
+
+import http.client
+
+from lazr.restful.declarations import (
+    error_status,
+    export_operation_as,
+    export_write_operation,
+    exported,
+    exported_as_webservice_entry,
+    mutator_for,
+    operation_for_version,
+    operation_parameters,
+    scoped,
+    )
+from lazr.restful.fields import Reference
+from lazr.restful.interface import copy_field
+from zope.interface import (
+    Attribute,
+    Interface,
+    )
+from zope.schema import (
+    Bytes,
+    Choice,
+    Datetime,
+    Int,
+    TextLine,
+    )
+from zope.security.interfaces import Unauthorized
+
+from lp import _
+from lp.app.validators.attachment import attachment_size_constraint
+from lp.code.enums import (
+    RevisionStatusArtifactType,
+    RevisionStatusResult,
+    )
+from lp.services.auth.enums import AccessTokenScope
+from lp.services.fields import URIField
+
+
+@error_status(http.client.UNAUTHORIZED)
+class RevisionStatusReportsFeatureDisabled(Unauthorized):
+    """Only certain users can access APIs for revision status reports."""
+
+    def __init__(self):
+        super().__init__(
+            "You do not have permission to create revision status reports")
+
+
+class IRevisionStatusReportView(Interface):
+    """`IRevisionStatusReport` attributes that require launchpad.View."""
+
+    id = Int(title=_("ID"), required=True, readonly=True)
+
+    date_created = exported(Datetime(
+        title=_("When the report was created."), required=True, readonly=True))
+    date_started = exported(Datetime(
+        title=_("When the report was started.")), readonly=False)
+    date_finished = exported(Datetime(
+        title=_("When the report has finished.")), readonly=False)
+
+
+class IRevisionStatusReportEditableAttributes(Interface):
+    """`IRevisionStatusReport` attributes that can be edited.
+
+    These attributes need launchpad.View to see, and launchpad.Edit to change.
+    """
+
+    title = exported(TextLine(
+        title=_("A short title for the report."), required=True,
+        readonly=False))
+
+    git_repository = exported(Reference(
+        title=_("The Git repository for which this report is built."),
+        # Really IGitRepository, patched in _schema_circular_imports.py.
+        schema=Interface, required=True, readonly=True))
+
+    commit_sha1 = exported(TextLine(
+        title=_("The Git commit for which this report is built."),
+        required=True, readonly=True))
+
+    url = exported(URIField(title=_("URL"), required=False, readonly=False,
+                            description=_("The external url of the report.")))
+
+    result_summary = exported(TextLine(
+        title=_("A short summary of the result."), required=False,
+        readonly=False))
+
+    result = exported(Choice(
+        title=_('Result of the report'),  readonly=True,
+        required=False, vocabulary=RevisionStatusResult))
+
+    @mutator_for(result)
+    @operation_parameters(result=copy_field(result))
+    @export_write_operation()
+    @operation_for_version("devel")
+    def transitionToNewResult(result):
+        """Set the RevisionStatusReport result.
+
+        Set the revision status report result."""
+
+
+class IRevisionStatusReportEdit(Interface):
+    """`IRevisionStatusReport` attributes that require launchpad.Edit."""
+
+    @operation_parameters(
+        log_data=Bytes(title=_("The content of the artifact in bytes."),
+                       constraint=attachment_size_constraint))
+    @scoped(AccessTokenScope.REPOSITORY_BUILD_STATUS.title)
+    @export_write_operation()
+    @export_operation_as(name="setLog")
+    @operation_for_version("devel")
+    def api_setLog(log_data):
+        """Set a new log on an existing status report.
+
+        :param log_data: The contents (in bytes) of the log.
+        """
+
+    @operation_parameters(
+        title=TextLine(title=_("A short title for the report."),
+                       required=False),
+        url=TextLine(title=_("The external link of the status report."),
+                     required=False),
+        result_summary=TextLine(title=_("A short summary of the result."),
+                                required=False),
+        result=Choice(vocabulary=RevisionStatusResult, required=False))
+    @scoped(AccessTokenScope.REPOSITORY_BUILD_STATUS.title)
+    @export_write_operation()
+    @operation_for_version("devel")
+    def update(title, url, result_summary, result):
+        """Updates a status report.
+
+        :param title: A short title for the report.
+        :param url: The external url of the report.
+        :param result_summary: A short summary of the result.
+        :param result: The result of the report.
+        """
+
+
+@exported_as_webservice_entry(as_of="beta")
+class IRevisionStatusReport(IRevisionStatusReportView,
+                            IRevisionStatusReportEditableAttributes,
+                            IRevisionStatusReportEdit):
+    """An revision status report for a Git commit."""
+
+
+class IRevisionStatusReportSet(Interface):
+    """The set of all revision status reports."""
+
+    def new(creator, title, git_repository, commit_sha1, date_created=None,
+            url=None, result_summary=None, result=None, date_started=None,
+            date_finished=None, log=None):
+        """Return a new revision status report.
+
+        :param title: A text string.
+        :param git_repository: An `IGitRepository` for which the report
+            is being created.
+        :param commit_sha1: The sha1 of the commit for which the report
+            is being created.
+        :param date_created: The date when the report is being created.
+        :param url: External URL to view result of report.
+        :param result_summary: A short summary of the result.
+        :param result: The result of the check job for this revision.
+        :param date_started: DateTime that report was started.
+        :param date_finished: DateTime that report was completed.
+        :param log: Stores the content of the artifact for this report.
+        """
+
+    def getByID(id):
+        """Returns the RevisionStatusReport for a given ID."""
+
+    def findByRepository(repository):
+        """Returns all `RevisionStatusReport` for a repository."""
+
+    def findByCommit(repository, commit_sha1):
+        """Returns all `RevisionStatusReport` for a repository and commit."""
+
+    def deleteForRepository(repository):
+        """Delete all `RevisionStatusReport` for a repository."""
+
+
+class IRevisionStatusArtifactSet(Interface):
+    """The set of all revision status artifacts."""
+
+    def new(lfa, report):
+        """Return a new revision status artifact.
+
+        :param lfa: An `ILibraryFileAlias`.
+        :param report: An `IRevisionStatusReport` for which the
+            artifact is being created.
+        """
+
+    def getByID(id):
+        """Returns the RevisionStatusArtifact for a given ID."""
+
+    def findByReport(report):
+        """Returns the set of artifacts for a given report."""
+
+
+class IRevisionStatusArtifact(Interface):
+    id = Int(title=_("ID"), required=True, readonly=True)
+
+    report = Attribute(
+        "The `RevisionStatusReport` that this artifact is linked to.")
+
+    library_file = Attribute(
+        "The `LibraryFileAlias` object containing information for "
+        "a revision status report.")
+
+    artifact_type = Choice(
+        title=_('The type of artifact, only log for now.'),
+        vocabulary=RevisionStatusArtifactType)
diff --git a/lib/lp/code/interfaces/webservice.py b/lib/lp/code/interfaces/webservice.py
index bd94298..bfe20b3 100644
--- a/lib/lp/code/interfaces/webservice.py
+++ b/lib/lp/code/interfaces/webservice.py
@@ -67,10 +67,10 @@ from lp.code.interfaces.gitref import IGitRef
 from lp.code.interfaces.gitrepository import (
     IGitRepository,
     IGitRepositorySet,
-    IRevisionStatusReport,
     )
 from lp.code.interfaces.gitsubscription import IGitSubscription
 from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
+from lp.code.interfaces.revisionstatus import IRevisionStatusReport
 from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
 from lp.code.interfaces.sourcepackagerecipebuild import (
     ISourcePackageRecipeBuild,
diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py
index 5a99c46..fca7dfa 100644
--- a/lib/lp/code/model/gitrepository.py
+++ b/lib/lp/code/model/gitrepository.py
@@ -6,7 +6,6 @@ __all__ = [
     'GitRepository',
     'GitRepositorySet',
     'parse_git_commits',
-    'RevisionStatusReport',
     ]
 
 from collections import (
@@ -20,7 +19,6 @@ from datetime import (
 import email
 from fnmatch import fnmatch
 from functools import partial
-import io
 from itertools import (
     chain,
     groupby,
@@ -104,8 +102,6 @@ from lp.code.enums import (
     GitPermissionType,
     GitRepositoryStatus,
     GitRepositoryType,
-    RevisionStatusArtifactType,
-    RevisionStatusResult,
     )
 from lp.code.errors import (
     CannotDeleteGitRepository,
@@ -135,10 +131,6 @@ from lp.code.interfaces.gitrepository import (
     GitIdentityMixin,
     IGitRepository,
     IGitRepositorySet,
-    IRevisionStatusArtifactSet,
-    IRevisionStatusReport,
-    IRevisionStatusReportSet,
-    RevisionStatusReportsFeatureDisabled,
     user_has_special_git_repository_access,
     )
 from lp.code.interfaces.gitrule import (
@@ -146,6 +138,10 @@ from lp.code.interfaces.gitrule import (
     is_rule_exact,
     )
 from lp.code.interfaces.revision import IRevisionSet
+from lp.code.interfaces.revisionstatus import (
+    IRevisionStatusReportSet,
+    RevisionStatusReportsFeatureDisabled,
+    )
 from lp.code.mail.branch import send_git_repository_modified_notifications
 from lp.code.model.branchmergeproposal import BranchMergeProposal
 from lp.code.model.gitactivity import GitActivity
@@ -158,6 +154,7 @@ from lp.code.model.gitrule import (
     GitRuleGrant,
     )
 from lp.code.model.gitsubscription import GitSubscription
+from lp.code.model.revisionstatus import RevisionStatusReport
 from lp.oci.interfaces.ocirecipe import IOCIRecipeSet
 from lp.registry.enums import PersonVisibility
 from lp.registry.errors import CannotChangeInformationType
@@ -197,10 +194,7 @@ from lp.services.database.constants import (
 from lp.services.database.decoratedresultset import DecoratedResultSet
 from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import (
-    convert_storm_clause_to_string,
-    get_transaction_timestamp,
-    )
+from lp.services.database.sqlbase import get_transaction_timestamp
 from lp.services.database.stormbase import StormBase
 from lp.services.database.stormexpr import (
     Array,
@@ -216,7 +210,6 @@ from lp.services.identity.interfaces.account import (
     )
 from lp.services.job.interfaces.job import JobStatus
 from lp.services.job.model.job import Job
-from lp.services.librarian.interfaces import ILibraryFileAliasSet
 from lp.services.macaroons.interfaces import IMacaroonIssuer
 from lp.services.macaroons.model import MacaroonIssuerBase
 from lp.services.mail.notificationrecipientset import NotificationRecipientSet
@@ -305,164 +298,6 @@ def git_repository_modified(repository, event):
     send_git_repository_modified_notifications(repository, event)
 
 
-@implementer(IRevisionStatusReport)
-class RevisionStatusReport(StormBase):
-    __storm_table__ = 'RevisionStatusReport'
-
-    id = Int(primary=True)
-
-    creator_id = Int(name="creator", allow_none=False)
-    creator = Reference(creator_id, "Person.id")
-
-    title = Unicode(name='name', allow_none=False)
-
-    git_repository_id = Int(name='git_repository', allow_none=False)
-    git_repository = Reference(git_repository_id, 'GitRepository.id')
-
-    commit_sha1 = Unicode(name='commit_sha1', allow_none=False)
-
-    url = Unicode(name='url', allow_none=True)
-
-    result_summary = Unicode(name='description', allow_none=True)
-
-    result = DBEnum(name='result', allow_none=True, enum=RevisionStatusResult)
-
-    date_created = DateTime(
-        name='date_created', tzinfo=pytz.UTC, allow_none=False)
-
-    date_started = DateTime(name='date_started', tzinfo=pytz.UTC,
-                            allow_none=True)
-    date_finished = DateTime(name='date_finished', tzinfo=pytz.UTC,
-                             allow_none=True)
-
-    def __init__(self, git_repository, user, title, commit_sha1,
-                 url, result_summary, result):
-        super().__init__()
-        self.creator = user
-        self.git_repository = git_repository
-        self.title = title
-        self.commit_sha1 = commit_sha1
-        self.url = url
-        self.result_summary = result_summary
-        self.date_created = UTC_NOW
-        self.transitionToNewResult(result)
-
-    def api_setLog(self, log_data):
-        filename = '%s-%s.txt' % (self.title, self.commit_sha1)
-
-        lfa = getUtility(ILibraryFileAliasSet).create(
-            name=filename, size=len(log_data),
-            file=io.BytesIO(log_data), contentType='text/plain')
-
-        getUtility(IRevisionStatusArtifactSet).new(lfa, self)
-
-    def transitionToNewResult(self, result):
-        if self.result == RevisionStatusResult.WAITING:
-            if result == RevisionStatusResult.RUNNING:
-                self.date_started == UTC_NOW
-        else:
-            self.date_finished = UTC_NOW
-        self.result = result
-
-    def update(self, title=None, url=None,
-               result_summary=None, result=None):
-        if title is not None:
-            self.title = title
-        if url is not None:
-            self.url = url
-        if result_summary is not None:
-            self.result_summary = result_summary
-        if result is not None:
-            self.transitionToNewResult(result)
-
-
-@implementer(IRevisionStatusReportSet)
-class RevisionStatusReportSet:
-
-    def new(self, creator, title, git_repository, commit_sha1,
-            url=None, result_summary=None, result=None,
-            date_started=None, date_finished=None, log=None):
-        """See `IRevisionStatusReportSet`."""
-        store = IStore(RevisionStatusReport)
-        report = RevisionStatusReport(git_repository, creator, title,
-                                      commit_sha1, url, result_summary,
-                                      result)
-        store.add(report)
-        return report
-
-    def getByID(self, id):
-        return IStore(
-            RevisionStatusReport).find(RevisionStatusReport, id=id).one()
-
-    def findByRepository(self, repository):
-        return IStore(RevisionStatusReport).find(
-            RevisionStatusReport,
-            RevisionStatusReport.git_repository == repository)
-
-    def findByCommit(self, repository, commit_sha1):
-        """Returns all `RevisionStatusReport` for a repository and commit."""
-        return IStore(RevisionStatusReport).find(
-            RevisionStatusReport,
-            git_repository=repository,
-            commit_sha1=commit_sha1).order_by(
-                RevisionStatusReport.date_created,
-                RevisionStatusReport.id)
-
-    def deleteForRepository(self, repository):
-        clauses = [
-            RevisionStatusArtifact.report == RevisionStatusReport.id,
-            RevisionStatusReport.git_repository == repository,
-            ]
-        where = convert_storm_clause_to_string(And(*clauses))
-        IStore(RevisionStatusArtifact).execute("""
-            DELETE FROM RevisionStatusArtifact
-            USING RevisionStatusReport
-            WHERE """ + where)
-        self.findByRepository(repository).remove()
-
-
-class RevisionStatusArtifact(StormBase):
-    __storm_table__ = 'RevisionStatusArtifact'
-
-    id = Int(primary=True)
-
-    library_file_id = Int(name='library_file', allow_none=False)
-    library_file = Reference(library_file_id, 'LibraryFileAlias.id')
-
-    report_id = Int(name='report', allow_none=False)
-    report = Reference(report_id, 'RevisionStatusReport.id')
-
-    artifact_type = DBEnum(name='type', allow_none=False,
-                           enum=RevisionStatusArtifactType)
-
-    def __init__(self, library_file, report):
-        super().__init__()
-        self.library_file = library_file
-        self.report = report
-        self.artifact_type = RevisionStatusArtifactType.LOG
-
-
-@implementer(IRevisionStatusArtifactSet)
-class RevisionStatusArtifactSet:
-
-    def new(self, lfa, report):
-        """See `IRevisionStatusArtifactSet`."""
-        store = IStore(RevisionStatusArtifact)
-        artifact = RevisionStatusArtifact(lfa, report)
-        store.add(artifact)
-        return artifact
-
-    def getById(self, id):
-        return IStore(RevisionStatusArtifact).find(
-            RevisionStatusArtifact,
-            RevisionStatusArtifact.id == id).one()
-
-    def findByReport(self, report):
-        return IStore(RevisionStatusArtifact).find(
-            RevisionStatusArtifact,
-            RevisionStatusArtifact.report == report)
-
-
 @implementer(IGitRepository, IHasOwner, IPrivacy, IInformationType)
 class GitRepository(StormBase, WebhookTargetMixin, AccessTokenTargetMixin,
                     GitIdentityMixin):
diff --git a/lib/lp/code/model/revisionstatus.py b/lib/lp/code/model/revisionstatus.py
new file mode 100644
index 0000000..78a9e1b
--- /dev/null
+++ b/lib/lp/code/model/revisionstatus.py
@@ -0,0 +1,193 @@
+# Copyright 2021-2022 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__all__ = [
+    'RevisionStatusReport',
+    ]
+
+import io
+
+import pytz
+from storm.locals import (
+    And,
+    DateTime,
+    Int,
+    Reference,
+    Unicode,
+    )
+from zope.component import getUtility
+from zope.interface import implementer
+
+from lp.code.enums import (
+    RevisionStatusArtifactType,
+    RevisionStatusResult,
+    )
+from lp.code.interfaces.revisionstatus import (
+    IRevisionStatusArtifactSet,
+    IRevisionStatusReport,
+    IRevisionStatusReportSet,
+    )
+from lp.services.database.constants import UTC_NOW
+from lp.services.database.enumcol import DBEnum
+from lp.services.database.interfaces import IStore
+from lp.services.database.sqlbase import convert_storm_clause_to_string
+from lp.services.database.stormbase import StormBase
+from lp.services.librarian.interfaces import ILibraryFileAliasSet
+
+
+@implementer(IRevisionStatusReport)
+class RevisionStatusReport(StormBase):
+    __storm_table__ = 'RevisionStatusReport'
+
+    id = Int(primary=True)
+
+    creator_id = Int(name="creator", allow_none=False)
+    creator = Reference(creator_id, "Person.id")
+
+    title = Unicode(name='name', allow_none=False)
+
+    git_repository_id = Int(name='git_repository', allow_none=False)
+    git_repository = Reference(git_repository_id, 'GitRepository.id')
+
+    commit_sha1 = Unicode(name='commit_sha1', allow_none=False)
+
+    url = Unicode(name='url', allow_none=True)
+
+    result_summary = Unicode(name='description', allow_none=True)
+
+    result = DBEnum(name='result', allow_none=True, enum=RevisionStatusResult)
+
+    date_created = DateTime(
+        name='date_created', tzinfo=pytz.UTC, allow_none=False)
+
+    date_started = DateTime(name='date_started', tzinfo=pytz.UTC,
+                            allow_none=True)
+    date_finished = DateTime(name='date_finished', tzinfo=pytz.UTC,
+                             allow_none=True)
+
+    def __init__(self, git_repository, user, title, commit_sha1,
+                 url, result_summary, result):
+        super().__init__()
+        self.creator = user
+        self.git_repository = git_repository
+        self.title = title
+        self.commit_sha1 = commit_sha1
+        self.url = url
+        self.result_summary = result_summary
+        self.date_created = UTC_NOW
+        self.transitionToNewResult(result)
+
+    def api_setLog(self, log_data):
+        filename = '%s-%s.txt' % (self.title, self.commit_sha1)
+
+        lfa = getUtility(ILibraryFileAliasSet).create(
+            name=filename, size=len(log_data),
+            file=io.BytesIO(log_data), contentType='text/plain')
+
+        getUtility(IRevisionStatusArtifactSet).new(lfa, self)
+
+    def transitionToNewResult(self, result):
+        if self.result == RevisionStatusResult.WAITING:
+            if result == RevisionStatusResult.RUNNING:
+                self.date_started == UTC_NOW
+        else:
+            self.date_finished = UTC_NOW
+        self.result = result
+
+    def update(self, title=None, url=None,
+               result_summary=None, result=None):
+        if title is not None:
+            self.title = title
+        if url is not None:
+            self.url = url
+        if result_summary is not None:
+            self.result_summary = result_summary
+        if result is not None:
+            self.transitionToNewResult(result)
+
+
+@implementer(IRevisionStatusReportSet)
+class RevisionStatusReportSet:
+
+    def new(self, creator, title, git_repository, commit_sha1,
+            url=None, result_summary=None, result=None,
+            date_started=None, date_finished=None, log=None):
+        """See `IRevisionStatusReportSet`."""
+        store = IStore(RevisionStatusReport)
+        report = RevisionStatusReport(git_repository, creator, title,
+                                      commit_sha1, url, result_summary,
+                                      result)
+        store.add(report)
+        return report
+
+    def getByID(self, id):
+        return IStore(
+            RevisionStatusReport).find(RevisionStatusReport, id=id).one()
+
+    def findByRepository(self, repository):
+        return IStore(RevisionStatusReport).find(
+            RevisionStatusReport,
+            RevisionStatusReport.git_repository == repository)
+
+    def findByCommit(self, repository, commit_sha1):
+        """Returns all `RevisionStatusReport` for a repository and commit."""
+        return IStore(RevisionStatusReport).find(
+            RevisionStatusReport,
+            git_repository=repository,
+            commit_sha1=commit_sha1).order_by(
+                RevisionStatusReport.date_created,
+                RevisionStatusReport.id)
+
+    def deleteForRepository(self, repository):
+        clauses = [
+            RevisionStatusArtifact.report == RevisionStatusReport.id,
+            RevisionStatusReport.git_repository == repository,
+            ]
+        where = convert_storm_clause_to_string(And(*clauses))
+        IStore(RevisionStatusArtifact).execute("""
+            DELETE FROM RevisionStatusArtifact
+            USING RevisionStatusReport
+            WHERE """ + where)
+        self.findByRepository(repository).remove()
+
+
+class RevisionStatusArtifact(StormBase):
+    __storm_table__ = 'RevisionStatusArtifact'
+
+    id = Int(primary=True)
+
+    library_file_id = Int(name='library_file', allow_none=False)
+    library_file = Reference(library_file_id, 'LibraryFileAlias.id')
+
+    report_id = Int(name='report', allow_none=False)
+    report = Reference(report_id, 'RevisionStatusReport.id')
+
+    artifact_type = DBEnum(name='type', allow_none=False,
+                           enum=RevisionStatusArtifactType)
+
+    def __init__(self, library_file, report):
+        super().__init__()
+        self.library_file = library_file
+        self.report = report
+        self.artifact_type = RevisionStatusArtifactType.LOG
+
+
+@implementer(IRevisionStatusArtifactSet)
+class RevisionStatusArtifactSet:
+
+    def new(self, lfa, report):
+        """See `IRevisionStatusArtifactSet`."""
+        store = IStore(RevisionStatusArtifact)
+        artifact = RevisionStatusArtifact(lfa, report)
+        store.add(artifact)
+        return artifact
+
+    def getById(self, id):
+        return IStore(RevisionStatusArtifact).find(
+            RevisionStatusArtifact,
+            RevisionStatusArtifact.id == id).one()
+
+    def findByReport(self, report):
+        return IStore(RevisionStatusArtifact).find(
+            RevisionStatusArtifact,
+            RevisionStatusArtifact.report == report)
diff --git a/lib/lp/code/model/tests/test_gitrepository.py b/lib/lp/code/model/tests/test_gitrepository.py
index 56f5394..15c3bac 100644
--- a/lib/lp/code/model/tests/test_gitrepository.py
+++ b/lib/lp/code/model/tests/test_gitrepository.py
@@ -10,7 +10,6 @@ from datetime import (
 import email
 from functools import partial
 import hashlib
-import io
 import json
 
 from breezy import urlutils
@@ -97,14 +96,16 @@ from lp.code.interfaces.gitrepository import (
     IGitRepository,
     IGitRepositorySet,
     IGitRepositoryView,
-    IRevisionStatusArtifactSet,
-    IRevisionStatusReportSet,
     )
 from lp.code.interfaces.gitrule import (
     IGitNascentRule,
     IGitNascentRuleGrant,
     )
 from lp.code.interfaces.revision import IRevisionSet
+from lp.code.interfaces.revisionstatus import (
+    IRevisionStatusArtifactSet,
+    IRevisionStatusReportSet,
+    )
 from lp.code.model.branchmergeproposal import BranchMergeProposal
 from lp.code.model.branchmergeproposaljob import (
     BranchMergeProposalJob,
@@ -5030,86 +5031,6 @@ class TestGitRepositoryWebservice(TestCaseWithFactory):
             response.body)
 
 
-class TestRevisionStatusReportWebservice(TestCaseWithFactory):
-    layer = LaunchpadFunctionalLayer
-
-    def setUp(self):
-        super().setUp()
-        self.repository = self.factory.makeGitRepository()
-        self.requester = self.repository.owner
-        self.title = self.factory.getUniqueUnicode('report-title')
-        self.commit_sha1 = hashlib.sha1(b"Some content").hexdigest()
-        self.result_summary = "120/120 tests passed"
-
-        self.report = self.factory.makeRevisionStatusReport(
-            user=self.repository.owner, git_repository=self.repository,
-            title=self.title, commit_sha1=self.commit_sha1,
-            result_summary=self.result_summary,
-            result=RevisionStatusResult.FAILED)
-
-        self.webservice = webservice_for_person(
-            None, default_api_version="devel")
-        with person_logged_in(self.requester):
-            self.report_url = api_url(self.report)
-
-            secret, _ = self.factory.makeAccessToken(
-                owner=self.requester, target=self.repository,
-                scopes=[AccessTokenScope.REPOSITORY_BUILD_STATUS])
-            self.header = {'Authorization': 'Token %s' % secret}
-
-    def test_setLogOnRevisionStatusReport(self):
-        content = b'log_content_data'
-        filesize = len(content)
-        sha1 = hashlib.sha1(content).hexdigest()
-        md5 = hashlib.md5(content).hexdigest()
-        response = self.webservice.named_post(
-            self.report_url, "setLog",
-            headers=self.header,
-            log_data=io.BytesIO(content))
-        self.assertEqual(200, response.status)
-
-        # A report may have multiple artifacts.
-        # We verify that the content we just submitted via API now
-        # matches one of the artifacts in the DB for the report.
-        with person_logged_in(self.requester):
-            artifacts = list(getUtility(
-                IRevisionStatusArtifactSet).findByReport(self.report))
-            lfcs = [artifact.library_file.content for artifact in artifacts]
-            sha1_of_all_artifacts = [lfc.sha1 for lfc in lfcs]
-            md5_of_all_artifacts = [lfc.md5 for lfc in lfcs]
-            filesizes_of_all_artifacts = [lfc.filesize for lfc in lfcs]
-
-            self.assertIn(sha1, sha1_of_all_artifacts)
-            self.assertIn(md5, md5_of_all_artifacts)
-            self.assertIn(filesize, filesizes_of_all_artifacts)
-
-    def test_update(self):
-        response = self.webservice.named_post(
-            self.report_url, "update",
-            headers=self.header,
-            title='updated-report-title')
-        self.assertEqual(200, response.status)
-        with person_logged_in(self.requester):
-            self.assertEqual('updated-report-title', self.report.title)
-            self.assertEqual(self.commit_sha1, self.report.commit_sha1)
-            self.assertEqual(self.result_summary, self.report.result_summary)
-            self.assertEqual(RevisionStatusResult.FAILED, self.report.result)
-            date_finished_before_update = self.report.date_finished
-        response = self.webservice.named_post(
-            self.report_url, "update",
-            headers=self.header,
-            result='Succeeded')
-        self.assertEqual(200, response.status)
-        with person_logged_in(self.requester):
-            self.assertEqual('updated-report-title', self.report.title)
-            self.assertEqual(self.commit_sha1, self.report.commit_sha1)
-            self.assertEqual(self.result_summary, self.report.result_summary)
-            self.assertEqual(RevisionStatusResult.SUCCEEDED,
-                             self.report.result)
-            self.assertGreater(self.report.date_finished,
-                               date_finished_before_update)
-
-
 class TestGitRepositoryMacaroonIssuer(MacaroonTestMixin, TestCaseWithFactory):
     """Test GitRepository macaroon issuing and verification."""
 
diff --git a/lib/lp/code/model/tests/test_revisionstatus.py b/lib/lp/code/model/tests/test_revisionstatus.py
new file mode 100644
index 0000000..4472988
--- /dev/null
+++ b/lib/lp/code/model/tests/test_revisionstatus.py
@@ -0,0 +1,100 @@
+# Copyright 2021-2022 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for revision status reports and artifacts."""
+
+import hashlib
+import io
+
+from zope.component import getUtility
+
+from lp.code.enums import RevisionStatusResult
+from lp.code.interfaces.revisionstatus import IRevisionStatusArtifactSet
+from lp.services.auth.enums import AccessTokenScope
+from lp.testing import (
+    api_url,
+    person_logged_in,
+    TestCaseWithFactory,
+    )
+from lp.testing.layers import LaunchpadFunctionalLayer
+from lp.testing.pages import webservice_for_person
+
+
+class TestRevisionStatusReportWebservice(TestCaseWithFactory):
+    layer = LaunchpadFunctionalLayer
+
+    def setUp(self):
+        super().setUp()
+        self.repository = self.factory.makeGitRepository()
+        self.requester = self.repository.owner
+        self.title = self.factory.getUniqueUnicode('report-title')
+        self.commit_sha1 = hashlib.sha1(b"Some content").hexdigest()
+        self.result_summary = "120/120 tests passed"
+
+        self.report = self.factory.makeRevisionStatusReport(
+            user=self.repository.owner, git_repository=self.repository,
+            title=self.title, commit_sha1=self.commit_sha1,
+            result_summary=self.result_summary,
+            result=RevisionStatusResult.FAILED)
+
+        self.webservice = webservice_for_person(
+            None, default_api_version="devel")
+        with person_logged_in(self.requester):
+            self.report_url = api_url(self.report)
+
+            secret, _ = self.factory.makeAccessToken(
+                owner=self.requester, target=self.repository,
+                scopes=[AccessTokenScope.REPOSITORY_BUILD_STATUS])
+            self.header = {'Authorization': 'Token %s' % secret}
+
+    def test_setLogOnRevisionStatusReport(self):
+        content = b'log_content_data'
+        filesize = len(content)
+        sha1 = hashlib.sha1(content).hexdigest()
+        md5 = hashlib.md5(content).hexdigest()
+        response = self.webservice.named_post(
+            self.report_url, "setLog",
+            headers=self.header,
+            log_data=io.BytesIO(content))
+        self.assertEqual(200, response.status)
+
+        # A report may have multiple artifacts.
+        # We verify that the content we just submitted via API now
+        # matches one of the artifacts in the DB for the report.
+        with person_logged_in(self.requester):
+            artifacts = list(getUtility(
+                IRevisionStatusArtifactSet).findByReport(self.report))
+            lfcs = [artifact.library_file.content for artifact in artifacts]
+            sha1_of_all_artifacts = [lfc.sha1 for lfc in lfcs]
+            md5_of_all_artifacts = [lfc.md5 for lfc in lfcs]
+            filesizes_of_all_artifacts = [lfc.filesize for lfc in lfcs]
+
+            self.assertIn(sha1, sha1_of_all_artifacts)
+            self.assertIn(md5, md5_of_all_artifacts)
+            self.assertIn(filesize, filesizes_of_all_artifacts)
+
+    def test_update(self):
+        response = self.webservice.named_post(
+            self.report_url, "update",
+            headers=self.header,
+            title='updated-report-title')
+        self.assertEqual(200, response.status)
+        with person_logged_in(self.requester):
+            self.assertEqual('updated-report-title', self.report.title)
+            self.assertEqual(self.commit_sha1, self.report.commit_sha1)
+            self.assertEqual(self.result_summary, self.report.result_summary)
+            self.assertEqual(RevisionStatusResult.FAILED, self.report.result)
+            date_finished_before_update = self.report.date_finished
+        response = self.webservice.named_post(
+            self.report_url, "update",
+            headers=self.header,
+            result='Succeeded')
+        self.assertEqual(200, response.status)
+        with person_logged_in(self.requester):
+            self.assertEqual('updated-report-title', self.report.title)
+            self.assertEqual(self.commit_sha1, self.report.commit_sha1)
+            self.assertEqual(self.result_summary, self.report.result_summary)
+            self.assertEqual(RevisionStatusResult.SUCCEEDED,
+                             self.report.result)
+            self.assertGreater(self.report.date_finished,
+                               date_finished_before_update)
diff --git a/lib/lp/security.py b/lib/lp/security.py
index 03c580f..5e74fd5 100644
--- a/lib/lp/security.py
+++ b/lib/lp/security.py
@@ -99,14 +99,16 @@ from lp.code.interfaces.gitcollection import IGitCollection
 from lp.code.interfaces.gitref import IGitRef
 from lp.code.interfaces.gitrepository import (
     IGitRepository,
-    IRevisionStatusArtifact,
-    IRevisionStatusReport,
     user_has_special_git_repository_access,
     )
 from lp.code.interfaces.gitrule import (
     IGitRule,
     IGitRuleGrant,
     )
+from lp.code.interfaces.revisionstatus import (
+    IRevisionStatusArtifact,
+    IRevisionStatusReport,
+    )
 from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
 from lp.code.interfaces.sourcepackagerecipebuild import (
     ISourcePackageRecipeBuild,
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 7df8d8a..dff6b0a 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -132,12 +132,13 @@ from lp.code.interfaces.gitref import (
     IGitRef,
     IGitRefRemoteSet,
     )
-from lp.code.interfaces.gitrepository import (
-    IGitRepository,
-    IRevisionStatusArtifactSet,
-    )
+from lp.code.interfaces.gitrepository import IGitRepository
 from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
 from lp.code.interfaces.revision import IRevisionSet
+from lp.code.interfaces.revisionstatus import (
+    IRevisionStatusArtifactSet,
+    IRevisionStatusReportSet,
+    )
 from lp.code.interfaces.sourcepackagerecipe import (
     ISourcePackageRecipeSource,
     MINIMAL_RECIPE_TEXT_BZR,
@@ -150,7 +151,6 @@ from lp.code.model.diff import (
     Diff,
     PreviewDiff,
     )
-from lp.code.model.gitrepository import IRevisionStatusReportSet
 from lp.oci.interfaces.ocipushrule import IOCIPushRuleSet
 from lp.oci.interfaces.ocirecipe import IOCIRecipeSet
 from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet