launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #24147
[Merge] ~cjwatson/launchpad:oci-git-owner-default into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:oci-git-owner-default into launchpad:master with ~cjwatson/launchpad:oci-git-more-tests as a prerequisite.
Commit message:
Add owner-default repositories for OCI projects
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1847444 in Launchpad itself: "Support OCI image building"
https://bugs.launchpad.net/launchpad/+bug/1847444
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/376142
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:oci-git-owner-default into launchpad:master.
diff --git a/lib/lp/code/adapters/gitcollection.py b/lib/lp/code/adapters/gitcollection.py
index 1b0f162..41282b2 100644
--- a/lib/lp/code/adapters/gitcollection.py
+++ b/lib/lp/code/adapters/gitcollection.py
@@ -7,8 +7,10 @@ __metaclass__ = type
__all__ = [
'git_collection_for_distribution',
'git_collection_for_distro_source_package',
+ 'git_collection_for_oci_project',
'git_collection_for_person',
'git_collection_for_person_distro_source_package',
+ 'git_collection_for_person_oci_project',
'git_collection_for_person_product',
'git_collection_for_project',
'git_collection_for_project_group',
@@ -41,6 +43,11 @@ def git_collection_for_distro_source_package(distro_source_package):
distro_source_package)
+def git_collection_for_oci_project(oci_project):
+ """Adapt an OCI project to a Git repository collection."""
+ return getUtility(IAllGitRepositories).inOCIProject(oci_project)
+
+
def git_collection_for_person(person):
"""Adapt a person to a Git repository collection."""
return getUtility(IAllGitRepositories).ownedBy(person)
@@ -60,3 +67,11 @@ def git_collection_for_person_distro_source_package(person_dsp):
collection = collection.inDistributionSourcePackage(
person_dsp.distro_source_package)
return collection
+
+
+def git_collection_for_person_oci_project(person_oci_project):
+ """Adapt a PersonOCIProject to a Git repository collection."""
+ collection = getUtility(IAllGitRepositories).ownedBy(
+ person_oci_project.person)
+ collection = collection.inOCIProject(person_oci_project.oci_project)
+ return collection
diff --git a/lib/lp/code/configure.zcml b/lib/lp/code/configure.zcml
index 8f50dce..d8ab7ee 100644
--- a/lib/lp/code/configure.zcml
+++ b/lib/lp/code/configure.zcml
@@ -1028,6 +1028,10 @@
provides="lp.code.interfaces.gitcollection.IGitCollection"
factory="lp.code.adapters.gitcollection.git_collection_for_distro_source_package"/>
<adapter
+ for="lp.registry.interfaces.ociproject.IOCIProject"
+ provides="lp.code.interfaces.gitcollection.IGitCollection"
+ factory="lp.code.adapters.gitcollection.git_collection_for_oci_project"/>
+ <adapter
for="lp.registry.interfaces.person.IPerson"
provides="lp.code.interfaces.gitcollection.IGitCollection"
factory="lp.code.adapters.gitcollection.git_collection_for_person"/>
@@ -1039,6 +1043,10 @@
for="lp.registry.interfaces.persondistributionsourcepackage.IPersonDistributionSourcePackage"
provides="lp.code.interfaces.gitcollection.IGitCollection"
factory="lp.code.adapters.gitcollection.git_collection_for_person_distro_source_package"/>
+ <adapter
+ for="lp.registry.interfaces.personociproject.IPersonOCIProject"
+ provides="lp.code.interfaces.gitcollection.IGitCollection"
+ factory="lp.code.adapters.gitcollection.git_collection_for_person_oci_project"/>
<securedutility
class="lp.code.model.gitcollection.GenericGitCollection"
provides="lp.code.interfaces.gitcollection.IAllGitRepositories">
@@ -1052,6 +1060,7 @@
<adapter factory="lp.code.model.defaultgit.OCIProjectDefaultGitRepository" />
<adapter factory="lp.code.model.defaultgit.OwnerProjectDefaultGitRepository" />
<adapter factory="lp.code.model.defaultgit.OwnerPackageDefaultGitRepository" />
+ <adapter factory="lp.code.model.defaultgit.OwnerOCIProjectDefaultGitRepository" />
<class class="lp.code.model.gitlookup.GitLookup">
<allow interface="lp.code.interfaces.gitlookup.IGitLookup" />
diff --git a/lib/lp/code/interfaces/gitrepository.py b/lib/lp/code/interfaces/gitrepository.py
index 7a6d9d9..1b35e21 100644
--- a/lib/lp/code/interfaces/gitrepository.py
+++ b/lib/lp/code/interfaces/gitrepository.py
@@ -73,11 +73,12 @@ from lp.code.interfaces.hasrecipes import IHasRecipes
from lp.registry.interfaces.distributionsourcepackage import (
IDistributionSourcePackage,
)
-from lp.registry.interfaces.ociprojectname import IOCIProjectName
+from lp.registry.interfaces.ociproject import IOCIProject
from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.persondistributionsourcepackage import (
IPersonDistributionSourcePackageFactory,
)
+from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
from lp.registry.interfaces.personproduct import IPersonProductFactory
from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.role import IPersonRoles
@@ -947,8 +948,9 @@ class IGitRepositorySet(Interface):
repository.
:param registrant: The `IPerson` who registered the new repository.
:param owner: The `IPerson` who owns the new repository.
- :param target: The `IProduct`, `IDistributionSourcePackage`, or
- `IPerson` that the new repository is associated with.
+ :param target: The `IProduct`, `IDistributionSourcePackage`,
+ `IOCIProjectName`, or `IPerson` that the new repository is
+ associated with.
:param name: The repository name.
:param information_type: Set the repository's information type to
one different from the target's default. The type must conform
@@ -1163,11 +1165,14 @@ class GitIdentityMixin:
elif IDistributionSourcePackage.providedBy(self.target):
factory = getUtility(IPersonDistributionSourcePackageFactory)
default = factory.create(self.owner, self.target)
+ elif IOCIProject.providedBy(self.target):
+ factory = getUtility(IPersonOCIProjectFactory)
+ default = factory.create(self.owner, self.target)
else:
# Also enforced by database constraint.
raise AssertionError(
- "Only projects or packages can have owner-target default "
- "repositories.")
+ "Only projects, packages, or OCI projects can have "
+ "owner-target default repositories.")
defaults.append(ICanHasDefaultGitRepository(default))
return sorted(defaults)
diff --git a/lib/lp/code/model/defaultgit.py b/lib/lp/code/model/defaultgit.py
index 0a87759..5862719 100644
--- a/lib/lp/code/model/defaultgit.py
+++ b/lib/lp/code/model/defaultgit.py
@@ -19,6 +19,7 @@ from lp.registry.interfaces.ociproject import IOCIProject
from lp.registry.interfaces.persondistributionsourcepackage import (
IPersonDistributionSourcePackage,
)
+from lp.registry.interfaces.personociproject import IPersonOCIProject
from lp.registry.interfaces.personproduct import IPersonProduct
from lp.registry.interfaces.product import IProduct
@@ -124,3 +125,22 @@ class OwnerPackageDefaultGitRepository(BaseDefaultGitRepository):
return "~%s/%s/+source/%s" % (
self.context.person.name, dsp.distribution.name,
dsp.sourcepackagename.name)
+
+
+@adapter(IPersonOCIProject)
+@implementer(ICanHasDefaultGitRepository)
+class OwnerOCIProjectDefaultGitRepository(BaseDefaultGitRepository):
+ """Implement an owner's default Git repository for an OCI project."""
+
+ sort_order = 1
+
+ def __init__(self, person_oci_project):
+ self.context = person_oci_project
+
+ @property
+ def path(self):
+ """See `ICanHasDefaultGitRepository`."""
+ oci_project = self.context.oci_project
+ return "~%s/%s/+oci/%s" % (
+ self.context.person.name, oci_project.pillar.name,
+ oci_project.name)
diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py
index 966db3a..2975d1d 100644
--- a/lib/lp/code/model/gitrepository.py
+++ b/lib/lp/code/model/gitrepository.py
@@ -1757,6 +1757,10 @@ class GitRepositorySet:
clauses.append(GitRepository.distribution == target.distribution)
clauses.append(
GitRepository.sourcepackagename == target.sourcepackagename)
+ elif IOCIProject.providedBy(target):
+ clauses.append(GitRepository.distribution == target.distribution)
+ clauses.append(
+ GitRepository.ociprojectname == target.ociprojectname)
else:
raise GitTargetError(
"Personal repositories cannot be defaults for any target.")
diff --git a/lib/lp/code/model/tests/test_gitcollection.py b/lib/lp/code/model/tests/test_gitcollection.py
index 91f6e4b..f96e8e8 100644
--- a/lib/lp/code/model/tests/test_gitcollection.py
+++ b/lib/lp/code/model/tests/test_gitcollection.py
@@ -44,6 +44,7 @@ from lp.registry.interfaces.person import TeamMembershipPolicy
from lp.registry.model.persondistributionsourcepackage import (
PersonDistributionSourcePackage,
)
+from lp.registry.model.personociproject import PersonOCIProject
from lp.registry.model.personproduct import PersonProduct
from lp.services.database.interfaces import IStore
from lp.services.webapp.publisher import canonical_url
@@ -85,6 +86,10 @@ class TestGitCollectionAdaptation(TestCaseWithFactory):
# collection.
self.assertCollection(self.factory.makeDistributionSourcePackage())
+ def test_oci_project(self):
+ # An OCI project can be adapted to a Git repository collection.
+ self.assertCollection(self.factory.makeOCIProject())
+
def test_person(self):
# A person can be adapted to a Git repository collection.
self.assertCollection(self.factory.makePerson())
@@ -101,6 +106,12 @@ class TestGitCollectionAdaptation(TestCaseWithFactory):
self.assertCollection(
PersonDistributionSourcePackage(dsp.distribution.owner, dsp))
+ def test_person_oci_project(self):
+ # A PersonOCIProject can be adapted to a Git repository collection.
+ oci_project = self.factory.makeOCIProject()
+ self.assertCollection(
+ PersonOCIProject(oci_project.pillar.owner, oci_project))
+
class TestGenericGitCollection(TestCaseWithFactory):
diff --git a/lib/lp/code/model/tests/test_gitrepository.py b/lib/lp/code/model/tests/test_gitrepository.py
index 991ba7b..acb5fff 100644
--- a/lib/lp/code/model/tests/test_gitrepository.py
+++ b/lib/lp/code/model/tests/test_gitrepository.py
@@ -136,6 +136,7 @@ from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.persondistributionsourcepackage import (
IPersonDistributionSourcePackageFactory,
)
+from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
from lp.registry.interfaces.personproduct import IPersonProductFactory
from lp.registry.tests.test_accesspolicy import get_policies_for_artifact
from lp.services.authserver.xmlrpc import AuthServerAPIView
@@ -604,6 +605,21 @@ class TestGitIdentityMixin(TestCaseWithFactory):
repository.owner.name, dsp.distribution.name,
dsp.sourcepackagename.name))
+ def test_git_identity_owner_default_for_oci_project(self):
+ # If a repository is a person's default for an OCI project, then its
+ # Git identity is a combination of the person name and the OCI
+ # project path.
+ oci_project = self.factory.makeOCIProject()
+ repository = self.factory.makeGitRepository(target=oci_project)
+ with person_logged_in(repository.owner) as user:
+ self.repository_set.setDefaultRepositoryForOwner(
+ repository.owner, oci_project, repository, user)
+ self.assertGitIdentity(
+ repository,
+ "~%s/%s/+oci/%s" % (
+ repository.owner.name, oci_project.pillar.name,
+ oci_project.name))
+
def test_identities_no_defaults(self):
# If there are no defaults, the only repository identity is the
# unique name.
@@ -663,7 +679,8 @@ class TestGitIdentityMixin(TestCaseWithFactory):
def test_default_for_oci_project(self):
# If a repository is the default for an OCI project, then that is
- # the preferred identity.
+ # the preferred identity. Target defaults are preferred over
+ # owner-target defaults.
mint = self.factory.makeDistribution(name="mint")
eric = self.factory.makePerson(name="eric")
mint_choc = self.factory.makeOCIProject(
@@ -672,13 +689,19 @@ class TestGitIdentityMixin(TestCaseWithFactory):
owner=eric, target=mint_choc, name="choc-repo")
oci_project = repository.target
with admin_logged_in():
+ self.repository_set.setDefaultRepositoryForOwner(
+ repository.owner, oci_project, repository, repository.owner)
self.repository_set.setDefaultRepository(
oci_project, repository, force_oci=True)
+ eric_oci_project = getUtility(IPersonOCIProjectFactory).create(
+ eric, oci_project)
self.assertEqual(
- [ICanHasDefaultGitRepository(oci_project)],
+ [ICanHasDefaultGitRepository(target)
+ for target in (oci_project, eric_oci_project)],
repository.getRepositoryDefaults())
self.assertEqual(
[("mint/+oci/choc", oci_project),
+ ("~eric/mint/+oci/choc", eric_oci_project),
("~eric/mint/+oci/choc/+git/choc-repo", repository)],
repository.getRepositoryIdentities())
@@ -3585,6 +3608,12 @@ class TestGitRepositorySetDefaultsOwnerPackage(
pass
+class TestGitRepositorySetDefaultsOwnerOCIProject(
+ TestGitRepositorySetDefaultsOwnerMixin,
+ TestGitRepositorySetDefaultsOCIProject):
+ pass
+
+
class TestGitRepositoryWebservice(TestCaseWithFactory):
"""Tests for the webservice."""
diff --git a/lib/lp/code/xmlrpc/tests/test_git.py b/lib/lp/code/xmlrpc/tests/test_git.py
index 106d1e9..8db278b 100644
--- a/lib/lp/code/xmlrpc/tests/test_git.py
+++ b/lib/lp/code/xmlrpc/tests/test_git.py
@@ -967,6 +967,44 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
self.assertTrue(repository.owner_default)
self.assertEqual(team, repository.owner)
+ def test_translatePath_create_oci_project_owner_default(self):
+ # A repository can be created and immediately set as its owner's
+ # default for an OCI project.
+ requester = self.factory.makePerson()
+ oci_project = self.factory.makeOCIProject()
+ path = u"/~%s/%s/+oci/%s" % (
+ requester.name, oci_project.pillar.name, oci_project.name)
+ repository = self.assertCreates(requester, path)
+ self.assertFalse(repository.target_default)
+ self.assertTrue(repository.owner_default)
+ self.assertEqual(requester, repository.owner)
+
+ def test_translatePath_create_oci_project_team_owner_default(self):
+ # The owner of a team can create a team-owned repository and
+ # immediately set it as that team's default for an OCI project.
+ requester = self.factory.makePerson()
+ team = self.factory.makeTeam(owner=requester)
+ oci_project = self.factory.makeOCIProject()
+ path = u"/~%s/%s/+oci/%s" % (
+ team.name, oci_project.pillar.name, oci_project.name)
+ repository = self.assertCreates(requester, path)
+ self.assertFalse(repository.target_default)
+ self.assertTrue(repository.owner_default)
+ self.assertEqual(team, repository.owner)
+
+ def test_translatePath_create_oci_project_team_member_default(self):
+ # A non-owner member of a team can create a team-owned repository
+ # and immediately set it as that team's default for an OCI project.
+ requester = self.factory.makePerson()
+ team = self.factory.makeTeam(members=[requester])
+ oci_project = self.factory.makeOCIProject()
+ path = u"/~%s/%s/+oci/%s" % (
+ team.name, oci_project.pillar.name, oci_project.name)
+ repository = self.assertCreates(requester, path)
+ self.assertFalse(repository.target_default)
+ self.assertTrue(repository.owner_default)
+ self.assertEqual(team, repository.owner)
+
def test_translatePath_create_broken_hosting_service(self):
# If the hosting service is down, trying to create a repository
# fails and doesn't leave junk around in the Launchpad database.
diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
index 1c86ace..e11c156 100644
--- a/lib/lp/registry/browser/person.py
+++ b/lib/lp/registry/browser/person.py
@@ -161,6 +161,7 @@ from lp.registry.interfaces.mailinglist import (
from lp.registry.interfaces.mailinglistsubscription import (
MailingListAutoSubscribePolicy,
)
+from lp.registry.interfaces.ociproject import IOCIProject
from lp.registry.interfaces.person import (
IPerson,
IPersonClaim,
@@ -169,6 +170,7 @@ from lp.registry.interfaces.person import (
from lp.registry.interfaces.persondistributionsourcepackage import (
IPersonDistributionSourcePackageFactory,
)
+from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
from lp.registry.interfaces.personproduct import IPersonProductFactory
from lp.registry.interfaces.persontransferjob import (
IPersonDeactivateJobSource,
@@ -392,19 +394,21 @@ class BranchTraversalMixin:
for _ in range(num_traversed):
self.request.stepstogo.consume()
+ using_pillar_alias = False
if IProduct.providedBy(target):
if target.name != pillar_name:
- # This repository was accessed through one of its
- # project's aliases, so we must redirect to its
- # canonical URL.
- return self.redirectSubTree(canonical_url(repository))
-
- if IDistributionSourcePackage.providedBy(target):
+ using_pillar_alias = True
+ elif IDistributionSourcePackage.providedBy(target):
if target.distribution.name != pillar_name:
- # This branch or repository was accessed through one of its
- # distribution's aliases, so we must redirect to its
- # canonical URL.
- return self.redirectSubTree(canonical_url(repository))
+ using_pillar_alias = True
+ elif IOCIProject.providedBy(target):
+ if target.pillar.name != pillar_name:
+ using_pillar_alias = True
+
+ if using_pillar_alias:
+ # This repository was accessed through one of its project's
+ # aliases, so we must redirect to its canonical URL.
+ return self.redirectSubTree(canonical_url(repository))
return repository
except (NotFoundError, InvalidNamespace, InvalidProductName):
@@ -412,7 +416,9 @@ class BranchTraversalMixin:
# If the pillar is a product, then return the PersonProduct; if it
# is a distribution and further segments provide a source package,
- # then return the PersonDistributionSourcePackage.
+ # then return the PersonDistributionSourcePackage; if it is a
+ # distribution and further segments provide an OCI project, then
+ # return the PersonOCIProject.
pillar = getUtility(IPillarNameSet).getByName(pillar_name)
if IProduct.providedBy(pillar):
person_product = getUtility(IPersonProductFactory).create(
@@ -424,24 +430,30 @@ class BranchTraversalMixin:
status=301)
getUtility(IOpenLaunchBag).add(pillar)
return person_product
- elif IDistribution.providedBy(pillar):
- if (len(self.request.stepstogo) >= 2 and
- self.request.stepstogo.peek() == "+source"):
+ elif (IDistribution.providedBy(pillar) and
+ len(self.request.stepstogo) >= 2):
+ if self.request.stepstogo.peek() == "+source":
+ get_target = IDistribution(pillar).getSourcePackage
+ factory = getUtility(IPersonDistributionSourcePackageFactory)
+ elif self.request.stepstogo.peek() == "+oci":
+ get_target = IDistribution(pillar).getOCIProject
+ factory = getUtility(IPersonOCIProjectFactory)
+ else:
+ get_target, factory = None, None
+ if get_target is not None:
self.request.stepstogo.consume()
spn_name = self.request.stepstogo.consume()
- dsp = IDistribution(pillar).getSourcePackage(spn_name)
- if dsp is not None:
- factory = getUtility(
- IPersonDistributionSourcePackageFactory)
- person_dsp = factory.create(self.context, dsp)
+ target = get_target(spn_name)
+ if target is not None:
+ person_target = factory.create(self.context, target)
# If accessed through an alias, redirect to the proper
# name.
if pillar.name != pillar_name:
return self.redirectSubTree(
- canonical_url(person_dsp, request=self.request),
+ canonical_url(person_target, request=self.request),
status=301)
getUtility(IOpenLaunchBag).add(pillar)
- return person_dsp
+ return person_target
# Otherwise look for a branch.
try:
diff --git a/lib/lp/registry/browser/tests/test_person.py b/lib/lp/registry/browser/tests/test_person.py
index cb6f95a..626cab4 100644
--- a/lib/lp/registry/browser/tests/test_person.py
+++ b/lib/lp/registry/browser/tests/test_person.py
@@ -163,6 +163,14 @@ class TestPersonNavigation(TestCaseWithFactory):
dsp.sourcepackagename.name, repository.name)
self.assertEqual(repository, test_traverse(url)[0])
+ def test_traverse_git_repository_oci_project(self):
+ oci_project = self.factory.makeOCIProject()
+ repository = self.factory.makeGitRepository(target=oci_project)
+ url = "/~%s/%s/+oci/%s/+git/%s" % (
+ repository.owner.name, oci_project.pillar.name, oci_project.name,
+ repository.name)
+ self.assertEqual(repository, test_traverse(url)[0])
+
def test_traverse_git_repository_personal(self):
person = self.factory.makePerson()
repository = self.factory.makeGitRepository(