launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #27967
[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