Merge ~twom/launchpad:oci-gitrepository into launchpad:master

Proposed by Tom Wardill
Status: Merged
Approved by: Colin Watson
Approved revision: d921f652f1f4baf8a58337d8b0209a1b10735513
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~twom/launchpad:oci-gitrepository
Merge into: launchpad:master
Diff against target: 745 lines (+370/-18)
17 files modified
lib/lp/code/configure.zcml (+4/-0)
lib/lp/code/interfaces/gitcollection.py (+3/-0)
lib/lp/code/interfaces/gitnamespace.py (+9/-1)
lib/lp/code/interfaces/gitrepository.py (+5/-3)
lib/lp/code/model/gitcollection.py (+13/-0)
lib/lp/code/model/gitnamespace.py (+111/-10)
lib/lp/code/model/gitrepository.py (+22/-2)
lib/lp/code/model/tests/test_gitcollection.py (+14/-0)
lib/lp/code/model/tests/test_gitnamespace.py (+133/-0)
lib/lp/code/model/tests/test_gitrepository.py (+14/-0)
lib/lp/registry/interfaces/distribution.py (+5/-0)
lib/lp/registry/interfaces/ociproject.py (+8/-2)
lib/lp/registry/model/distribution.py (+6/-0)
lib/lp/registry/model/ociproject.py (+4/-0)
lib/lp/registry/tests/test_distribution.py (+8/-0)
lib/lp/registry/tests/test_ociproject.py (+6/-0)
lib/lp/security.py (+5/-0)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+375021@code.launchpad.net

Commit message

Add GitNamespace for OCIProjects

Description of the change

The addition of the ociprojectname column to GitRepository requires a working Namespace implementation.

Add the model, following the pattern from DistributionSourcePackage.
Add tests for the model
Add required helper methods to IDistribution

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

There's a bit of an impedance mismatch between the design of OCIProject and what various things here expect, isn't there? Probably not much we can do about that (it's better than duplicating the table for project-based OCIProjects), but let's tighten things up a bit.

You've also missed a few bits, some of which should be done now and some of which can probably be done later, but I'll list all the ones I can think of so that they make it onto your to-do list somewhere:

 * GenericGitCollection.preloadDataForRepositories needs to preload related OCIProjectName rows.
 * The third paragraph of the docstring of IGitRepositoryView.getRepositoryIdentities needs updating.
 * It doesn't look like you've touched the lookup machinery that allows you to get a repository by its unique name or path. It's fine to do that in a separate merge proposal if you prefer, but it should come soon. When you do, remember to update the docstrings of IGitLookup.getByUniqueName and IGitRepositorySet.getByPath.
 * lp.code.browser.gitlisting will need attention so that it's possible to list repositories for an OCIProject. This will probably have to be in a separate merge proposal, since I don't think you have the navigation machinery for OCIProjects yet.
 * lp.code.browser.widgets.gitrepositorytarget will need attention. This can definitely be later, once we're ready for people to create these repositories.

review: Needs Fixing
~twom/launchpad:oci-gitrepository updated
55011ed... by Tom Wardill

Rename OCIProjectGitNamespace to DistributionOCIGitNamespace

43c54f9... by Tom Wardill

Assert for IDistribution in gitcollection

50f513c... by Tom Wardill

Ensure we have a distribution in gitnamespace

0874f50... by Tom Wardill

Add name property to OCIProject

575049c... by Tom Wardill

At most one target

a1fa70f... by Tom Wardill

Assert we have the right thing

824d2b9... by Tom Wardill

Gitnamespace test fixes

07ce4ee... by Tom Wardill

Test cleanups, no need to use ociprojectname everywhere

77ce71f... by Tom Wardill

Preload OCIProjectName

95cf373... by Tom Wardill

Update docstring to mention OCI projects

Revision history for this message
Colin Watson (cjwatson) :
review: Approve
Revision history for this message
Colin Watson (cjwatson) wrote :

Note that the corresponding DB patch is still pending review (https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/374672).

~twom/launchpad:oci-gitrepository updated
da868e2... by Tom Wardill

Ordering tweaks

2c4646f... by Tom Wardill

Better AssertionError

6e6544c... by Tom Wardill

Add more test, inherit in the right place

Revision history for this message
Colin Watson (cjwatson) :
~twom/launchpad:oci-gitrepository updated
689f5e6... by Tom Wardill

Don't export this yet

635ea87... by Tom Wardill

Remove interface declaration for ociprojectname entirely

Revision history for this message
Colin Watson (cjwatson) :
~twom/launchpad:oci-gitrepository updated
d921f65... by Tom Wardill

Fix failing distribution test

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/code/configure.zcml b/lib/lp/code/configure.zcml
2index 0ae4a3c..ef98d7f 100644
3--- a/lib/lp/code/configure.zcml
4+++ b/lib/lp/code/configure.zcml
5@@ -894,6 +894,10 @@
6 <allow interface="lp.code.interfaces.gitnamespace.IGitNamespace" />
7 <allow interface="lp.code.interfaces.gitnamespace.IGitNamespacePolicy" />
8 </class>
9+ <class class="lp.code.model.gitnamespace.DistributionOCIGitNamespace">
10+ <allow interface="lp.code.interfaces.gitnamespace.IGitNamespace" />
11+ <allow interface="lp.code.interfaces.gitnamespace.IGitNamespacePolicy" />
12+ </class>
13 <securedutility
14 class="lp.code.model.gitnamespace.GitNamespaceSet"
15 provides="lp.code.interfaces.gitnamespace.IGitNamespaceSet">
16diff --git a/lib/lp/code/interfaces/gitcollection.py b/lib/lp/code/interfaces/gitcollection.py
17index 4ab976b..90ab25e 100644
18--- a/lib/lp/code/interfaces/gitcollection.py
19+++ b/lib/lp/code/interfaces/gitcollection.py
20@@ -138,6 +138,9 @@ class IGitCollection(Interface):
21 def inDistributionSourcePackage(distro_source_package):
22 """Restrict to repositories in a package for a distribution."""
23
24+ def inOCIProject(oci_project):
25+ """Restrict to repositories in an OCI Project."""
26+
27 def isPersonal():
28 """Restrict the collection to personal repositories."""
29
30diff --git a/lib/lp/code/interfaces/gitnamespace.py b/lib/lp/code/interfaces/gitnamespace.py
31index 99841d7..eb4ebc4 100644
32--- a/lib/lp/code/interfaces/gitnamespace.py
33+++ b/lib/lp/code/interfaces/gitnamespace.py
34@@ -22,6 +22,7 @@ from lp.code.errors import InvalidNamespace
35 from lp.registry.interfaces.distributionsourcepackage import (
36 IDistributionSourcePackage,
37 )
38+from lp.registry.interfaces.ociproject import IOCIProject
39 from lp.registry.interfaces.person import IPerson
40 from lp.registry.interfaces.product import IProduct
41
42@@ -198,7 +199,8 @@ class IGitNamespacePolicy(Interface):
43 class IGitNamespaceSet(Interface):
44 """Interface for getting Git repository namespaces."""
45
46- def get(person, project=None, distribution=None, sourcepackagename=None):
47+ def get(person, project=None, distribution=None, sourcepackagename=None,
48+ ociprojectname=None):
49 """Return the appropriate `IGitNamespace` for the given objects."""
50
51
52@@ -209,6 +211,12 @@ def get_git_namespace(target, owner):
53 return getUtility(IGitNamespaceSet).get(
54 owner, distribution=target.distribution,
55 sourcepackagename=target.sourcepackagename)
56+ elif IOCIProject.providedBy(target):
57+ # This will eventually be allowable, but is not right now.
58+ assert target.distribution is not None
59+ return getUtility(IGitNamespaceSet).get(
60+ owner, distribution=target.distribution,
61+ ociprojectname=target.ociprojectname)
62 elif target is None or IPerson.providedBy(target):
63 return getUtility(IGitNamespaceSet).get(owner)
64 else:
65diff --git a/lib/lp/code/interfaces/gitrepository.py b/lib/lp/code/interfaces/gitrepository.py
66index 2fca251..25be4a5 100644
67--- a/lib/lp/code/interfaces/gitrepository.py
68+++ b/lib/lp/code/interfaces/gitrepository.py
69@@ -73,6 +73,7 @@ from lp.code.interfaces.hasrecipes import IHasRecipes
70 from lp.registry.interfaces.distributionsourcepackage import (
71 IDistributionSourcePackage,
72 )
73+from lp.registry.interfaces.ociprojectname import IOCIProjectName
74 from lp.registry.interfaces.person import IPerson
75 from lp.registry.interfaces.persondistributionsourcepackage import (
76 IPersonDistributionSourcePackageFactory,
77@@ -406,9 +407,10 @@ class IGitRepositoryView(IHasRecipes):
78 itself. For default repositories, the context object is the
79 appropriate default object.
80
81- Where a repository is the default for a product or a distribution
82- source package, the repository is available through a number of
83- different URLs. These URLs are the aliases for the repository.
84+ Where a repository is the default for a product, distribution
85+ source package or OCI project, the repository is available
86+ through a number of different URLs.
87+ These URLs are the aliases for the repository.
88
89 For example, a repository which is the default for the 'fooix'
90 project and which is also its owner's default repository for that
91diff --git a/lib/lp/code/model/gitcollection.py b/lib/lp/code/model/gitcollection.py
92index 70f6d01..552ea24 100644
93--- a/lib/lp/code/model/gitcollection.py
94+++ b/lib/lp/code/model/gitcollection.py
95@@ -52,7 +52,9 @@ from lp.code.model.gitrepository import (
96 from lp.code.model.gitrule import GitRuleGrant
97 from lp.code.model.gitsubscription import GitSubscription
98 from lp.registry.enums import EXCLUSIVE_TEAM_POLICY
99+from lp.registry.interfaces.distribution import IDistribution
100 from lp.registry.model.distribution import Distribution
101+from lp.registry.model.ociprojectname import OCIProjectName
102 from lp.registry.model.person import Person
103 from lp.registry.model.product import Product
104 from lp.registry.model.sourcepackagename import SourcePackageName
105@@ -199,6 +201,7 @@ class GenericGitCollection:
106 load_related(Distribution, repositories, ['distribution_id'])
107 load_related(SourcePackageName, repositories, ['sourcepackagename_id'])
108 load_related(Product, repositories, ['project_id'])
109+ load_related(OCIProjectName, repositories, ['ociprojectname_id'])
110 caches = {
111 repository.id: get_property_cache(repository)
112 for repository in repositories}
113@@ -458,6 +461,16 @@ class GenericGitCollection:
114 [GitRepository.distribution == distribution,
115 GitRepository.sourcepackagename == sourcepackagename])
116
117+ def inOCIProject(self, oci_project):
118+ """See `IGitCollection`."""
119+ # XXX twom 2019-11-01 This will eventually have project support
120+ assert IDistribution.providedBy(oci_project.pillar)
121+ distribution = oci_project.pillar
122+ ociprojectname = oci_project.ociprojectname
123+ return self._filterBy(
124+ [GitRepository.distribution == distribution,
125+ GitRepository.ociprojectname == ociprojectname])
126+
127 def isPersonal(self):
128 """See `IGitCollection`."""
129 return self._filterBy(
130diff --git a/lib/lp/code/model/gitnamespace.py b/lib/lp/code/model/gitnamespace.py
131index 4fb56e3..a255c5a 100644
132--- a/lib/lp/code/model/gitnamespace.py
133+++ b/lib/lp/code/model/gitnamespace.py
134@@ -5,6 +5,7 @@
135
136 __metaclass__ = type
137 __all__ = [
138+ 'DistributionOCIGitNamespace',
139 'GitNamespaceSet',
140 'PackageGitNamespace',
141 'PersonalGitNamespace',
142@@ -316,6 +317,7 @@ class PersonalGitNamespace(_BaseGitNamespace):
143 repository.project = None
144 repository.distribution = None
145 repository.sourcepackagename = None
146+ repository.ociprojectname = None
147 repository.target_default = False
148 repository.owner_default = False
149
150@@ -396,6 +398,7 @@ class ProjectGitNamespace(_BaseGitNamespace):
151 repository.project = self.project
152 repository.distribution = None
153 repository.sourcepackagename = None
154+ repository.ociprojectname = None
155
156 def getAllowedInformationTypes(self, who=None):
157 """See `IGitNamespace`."""
158@@ -491,6 +494,7 @@ class PackageGitNamespace(_BaseGitNamespace):
159 repository.project = None
160 repository.distribution = dsp.distribution
161 repository.sourcepackagename = dsp.sourcepackagename
162+ repository.ociprojectname = None
163
164 def getAllowedInformationTypes(self, who=None):
165 """See `IGitNamespace`."""
166@@ -538,24 +542,121 @@ class PackageGitNamespace(_BaseGitNamespace):
167 self_dsp.sourcepackagename == other_dsp.sourcepackagename)
168
169
170+@implementer(IGitNamespace, IGitNamespacePolicy)
171+class DistributionOCIGitNamespace(_BaseGitNamespace):
172+ """A namespace for OCI Project repositories.
173+
174+ This namespace is for all the repositories owned by a particular person
175+ in a particular OCI Project in a particular distribution.
176+ """
177+
178+ has_defaults = True
179+ allow_push_to_set_default = False
180+ supports_merge_proposals = True
181+ supports_code_imports = True
182+ allow_recipe_name_from_target = True
183+
184+ def __init__(self, person, oci_project):
185+ self.owner = person
186+ # Ensure we have a valid target for this namespace
187+ assert oci_project.distribution is not None
188+ self.oci_project = oci_project
189+
190+ def _getRepositoriesClause(self):
191+ return And(
192+ GitRepository.owner == self.owner,
193+ GitRepository.distribution == self.oci_project.distribution,
194+ GitRepository.ociprojectname == self.oci_project.ociprojectname)
195+
196+ # Marker for references to Git URL layouts: ##GITNAMESPACE##
197+ @property
198+ def name(self):
199+ """See `IGitNamespace`."""
200+ ocip = self.oci_project
201+ return '~%s/%s/+oci/%s' % (
202+ self.owner.name, ocip.pillar.name, ocip.name)
203+
204+ @property
205+ def target(self):
206+ """See `IGitNamespace`."""
207+ return IHasGitRepositories(self.oci_project)
208+
209+ def _retargetRepository(self, repository):
210+ ocip = self.oci_project
211+ repository.project = None
212+ repository.distribution = ocip.distribution
213+ repository.sourcepackagename = None
214+ repository.ociprojectname = ocip.ociprojectname
215+
216+ def getAllowedInformationTypes(self, who=None):
217+ """See `IGitNamespace`."""
218+ return PUBLIC_INFORMATION_TYPES
219+
220+ def getDefaultInformationType(self, who=None):
221+ """See `IGitNamespace`."""
222+ return InformationType.PUBLIC
223+
224+ def areRepositoriesMergeable(self, this, other):
225+ """See `IGitNamespacePolicy`."""
226+ # Repositories are mergeable into an OCI Project repository if the
227+ # OCI Project is the same.
228+ # XXX cjwatson 2015-04-18: Allow merging from a project repository
229+ # if any (active?) series links this OCI Project to that project.
230+ if this.namespace != self:
231+ raise AssertionError(
232+ "Namespace of %s is not %s." % (this.unique_name, self.name))
233+ other_namespace = other.namespace
234+ if zope_isinstance(other_namespace, DistributionOCIGitNamespace):
235+ return self.target == other_namespace.target
236+ else:
237+ return False
238+
239+ @property
240+ def collection(self):
241+ """See `IGitNamespacePolicy`."""
242+ return getUtility(IAllGitRepositories).inOCIProject(
243+ self.oci_project)
244+
245+ def assignKarma(self, person, action_name, date_created=None):
246+ """See `IGitNamespacePolicy`."""
247+ # Does nothing. Currently no karma for OCI Project Namespaces.
248+ return None
249+
250+
251 @implementer(IGitNamespaceSet)
252 class GitNamespaceSet:
253 """Only implementation of `IGitNamespaceSet`."""
254
255 def get(self, person, project=None, distribution=None,
256- sourcepackagename=None):
257+ sourcepackagename=None, ociprojectname=None):
258 """See `IGitNamespaceSet`."""
259 if project is not None:
260- assert distribution is None and sourcepackagename is None, (
261- "project implies no distribution or sourcepackagename. "
262- "Got %r, %r, %r."
263- % (project, distribution, sourcepackagename))
264+ assert (distribution is None and sourcepackagename is None
265+ and ociprojectname is None), (
266+ "project implies no distribution, sourcepackagename"
267+ " or ociprojectname. "
268+ "Got %r, %r, %r, %r."
269+ % (project, distribution, sourcepackagename, ociprojectname))
270 return ProjectGitNamespace(person, project)
271 elif distribution is not None:
272- assert sourcepackagename is not None, (
273- "distribution implies sourcepackagename. Got %r, %r"
274- % (distribution, sourcepackagename))
275- return PackageGitNamespace(
276- person, distribution.getSourcePackage(sourcepackagename))
277+ assert (sourcepackagename is None or ociprojectname is None), (
278+ "One of sourcepackagename and ociprojectname must be set."
279+ "Got %r, %r."
280+ % (sourcepackagename, ociprojectname))
281+ assert not (sourcepackagename and ociprojectname), (
282+ "Only one of sourcepackagename and ociprojectname can be set."
283+ "Got %r, %r."
284+ % (sourcepackagename, ociprojectname))
285+ if sourcepackagename is not None:
286+ return PackageGitNamespace(
287+ person, distribution.getSourcePackage(sourcepackagename))
288+ elif ociprojectname is not None:
289+ return DistributionOCIGitNamespace(
290+ person, distribution.getOCIProject(ociprojectname.name))
291+ else:
292+ raise AssertionError(
293+ "distribution implies sourcepackagename or "
294+ "ociprojectname. Got %r, %r, %r"
295+ % (distribution, sourcepackagename, ociprojectname))
296 else:
297 return PersonalGitNamespace(person)
298diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py
299index 93a2d16..92b6308 100644
300--- a/lib/lp/code/model/gitrepository.py
301+++ b/lib/lp/code/model/gitrepository.py
302@@ -150,9 +150,11 @@ from lp.registry.interfaces.accesspolicy import (
303 IAccessArtifactSource,
304 IAccessPolicySource,
305 )
306+from lp.registry.interfaces.distribution import IDistribution
307 from lp.registry.interfaces.distributionsourcepackage import (
308 IDistributionSourcePackage,
309 )
310+from lp.registry.interfaces.ociproject import IOCIProject
311 from lp.registry.interfaces.person import (
312 IPerson,
313 IPersonSet,
314@@ -308,6 +310,9 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin):
315 sourcepackagename_id = Int(name='sourcepackagename', allow_none=True)
316 sourcepackagename = Reference(sourcepackagename_id, 'SourcePackageName.id')
317
318+ ociprojectname_id = Int(name='ociprojectname', allow_none=True)
319+ ociprojectname = Reference(ociprojectname_id, 'OCIProjectName.id')
320+
321 name = Unicode(name='name', allow_none=False)
322
323 description = Unicode(name='description', allow_none=True)
324@@ -339,6 +344,11 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin):
325 elif IDistributionSourcePackage.providedBy(target):
326 self.distribution = target.distribution
327 self.sourcepackagename = target.sourcepackagename
328+ elif IOCIProject.providedBy(target):
329+ # XXX twom 2019-10-28 This should have support for product
330+ assert IDistribution.providedBy(target.pillar)
331+ self.ociprojectname = target.ociprojectname
332+ self.distribution = target.pillar
333 self.owner_default = False
334 self.target_default = False
335
336@@ -367,10 +377,16 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin):
337 if self.project is not None:
338 fmt = "~%(owner)s/%(project)s"
339 names["project"] = self.project.name
340- elif self.distribution is not None:
341+ elif (self.distribution is not None
342+ and self.sourcepackagename is not None):
343 fmt = "~%(owner)s/%(distribution)s/+source/%(source)s"
344 names["distribution"] = self.distribution.name
345 names["source"] = self.sourcepackagename.name
346+ elif (self.distribution is not None
347+ and self.ociprojectname is not None):
348+ fmt = "~%(owner)s/%(distribution)s/+oci/%(ociproject)s"
349+ names["distribution"] = self.distribution.name
350+ names["ociproject"] = self.ociprojectname.name
351 else:
352 fmt = "~%(owner)s"
353 fmt += "/+git/%(repository)s"
354@@ -384,8 +400,12 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin):
355 """See `IGitRepository`."""
356 if self.project is not None:
357 return self.project
358- elif self.distribution is not None:
359+ elif (self.distribution is not None
360+ and self.sourcepackagename is not None):
361 return self.distribution.getSourcePackage(self.sourcepackagename)
362+ elif (self.distribution is not None
363+ and self.ociprojectname is not None):
364+ return self.distribution.getOCIProject(self.ociprojectname.name)
365 else:
366 return self.owner
367
368diff --git a/lib/lp/code/model/tests/test_gitcollection.py b/lib/lp/code/model/tests/test_gitcollection.py
369index bd80685..91f6e4b 100644
370--- a/lib/lp/code/model/tests/test_gitcollection.py
371+++ b/lib/lp/code/model/tests/test_gitcollection.py
372@@ -381,6 +381,20 @@ class TestGitCollectionFilters(TestCaseWithFactory):
373 sorted([repository, repository2]),
374 sorted(collection.getRepositories()))
375
376+ def test_in_oci_project(self):
377+ # 'inOCIProject' returns a new collection that only
378+ # has repositories for the oci project in the distribution.
379+ ocip = self.factory.makeOCIProject()
380+ ocip_other_distro = self.factory.makeOCIProject()
381+ repository = self.factory.makeGitRepository(target=ocip)
382+ repository2 = self.factory.makeGitRepository(target=ocip)
383+ self.factory.makeGitRepository(target=ocip_other_distro)
384+ self.factory.makeGitRepository()
385+ collection = self.all_repositories.inOCIProject(ocip)
386+ self.assertEqual(
387+ sorted([repository, repository2]),
388+ sorted(collection.getRepositories()))
389+
390 def test_withIds(self):
391 # 'withIds' returns a new collection that only has repositories with
392 # the given ids.
393diff --git a/lib/lp/code/model/tests/test_gitnamespace.py b/lib/lp/code/model/tests/test_gitnamespace.py
394index 8e8696f..4f18523 100644
395--- a/lib/lp/code/model/tests/test_gitnamespace.py
396+++ b/lib/lp/code/model/tests/test_gitnamespace.py
397@@ -31,6 +31,7 @@ from lp.code.interfaces.gitnamespace import (
398 )
399 from lp.code.interfaces.gitrepository import IGitRepositorySet
400 from lp.code.model.gitnamespace import (
401+ DistributionOCIGitNamespace,
402 PackageGitNamespace,
403 PersonalGitNamespace,
404 ProjectGitNamespace,
405@@ -325,6 +326,15 @@ class TestPersonalGitNamespace(TestCaseWithFactory, NamespaceMixin):
406 other = self.factory.makeGitRepository(owner=owner, target=dsp)
407 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
408
409+ def test_areRepositoriesMergeable_oci_project(self):
410+ # OCI Project repositories are not mergeable into personal
411+ # repositories.
412+ owner = self.factory.makePerson()
413+ this = self.factory.makeGitRepository(owner=owner, target=owner)
414+ oci_project = self.factory.makeOCIProject()
415+ other = self.factory.makeGitRepository(owner=owner, target=oci_project)
416+ self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
417+
418 def test_collection(self):
419 # A personal namespace's collection is of personal repositories with
420 # the same owner.
421@@ -420,6 +430,15 @@ class TestProjectGitNamespace(TestCaseWithFactory, NamespaceMixin):
422 other = self.factory.makeGitRepository(owner=owner, target=dsp)
423 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
424
425+ def test_areRepositoriesMergeable_oci_project(self):
426+ # OCI Project repositories are not mergeable into project repositories.
427+ owner = self.factory.makePerson()
428+ project = self.factory.makeProduct()
429+ this = self.factory.makeGitRepository(owner=owner, target=project)
430+ oci_project = self.factory.makeOCIProject()
431+ other = self.factory.makeGitRepository(owner=owner, target=oci_project)
432+ self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
433+
434 def test_collection(self):
435 # A project namespace's collection is of repositories for the same
436 # project.
437@@ -436,6 +455,111 @@ class TestProjectGitNamespace(TestCaseWithFactory, NamespaceMixin):
438 repositories[0].namespace.collection.getRepositories())
439
440
441+class TestDistributionOCIGitNamespace(TestCaseWithFactory, NamespaceMixin):
442+ """Tests for `DistributionOCIGitNamespace`."""
443+
444+ layer = DatabaseFunctionalLayer
445+
446+ def getNamespace(self, person=None):
447+ if person is None:
448+ person = self.factory.makePerson()
449+ return get_git_namespace(self.factory.makeOCIProject(), person)
450+
451+ def test_name(self):
452+ person = self.factory.makePerson()
453+ oci_project = self.factory.makeOCIProject()
454+ namespace = DistributionOCIGitNamespace(person, oci_project)
455+ self.assertEqual(
456+ "~%s/%s/+oci/%s" % (
457+ person.name, oci_project.distribution.name,
458+ oci_project.ociprojectname.name),
459+ namespace.name)
460+
461+ def test_owner(self):
462+ # The person passed to an oci project namespace is the owner.
463+ person = self.factory.makePerson()
464+ oci_project = self.factory.makeOCIProject()
465+ namespace = DistributionOCIGitNamespace(person, oci_project)
466+ self.assertEqual(person, removeSecurityProxy(namespace).owner)
467+
468+ def test_target(self):
469+ # The target for an oci project namespace is the oci project.
470+ person = self.factory.makePerson()
471+ oci_project = self.factory.makeOCIProject()
472+ namespace = DistributionOCIGitNamespace(person, oci_project)
473+ self.assertEqual(oci_project, namespace.target)
474+
475+ def test_supports_merge_proposals(self):
476+ # OCI Project namespaces support merge proposals.
477+ self.assertTrue(self.getNamespace().supports_merge_proposals)
478+
479+ def test_areRepositoriesMergeable_same_repository(self):
480+ # An OCI Project repository is mergeable into itself.
481+ oci_project = self.factory.makeOCIProject()
482+ repository = self.factory.makeGitRepository(target=oci_project)
483+ self.assertTrue(
484+ repository.namespace.areRepositoriesMergeable(
485+ repository, repository))
486+
487+ def test_areRepositoriesMergeable_same_namespace(self):
488+ # Repositories of the same OCI Project are mergeable.
489+ oci_project = self.factory.makeOCIProject()
490+ this = self.factory.makeGitRepository(target=oci_project)
491+ other = self.factory.makeGitRepository(target=oci_project)
492+ self.assertTrue(this.namespace.areRepositoriesMergeable(this, other))
493+
494+ def test_areRepositoriesMergeable_different_namespace(self):
495+ # Repositories of a different OCI Project are not mergeable.
496+ this_oci_project = self.factory.makeOCIProject()
497+ this = self.factory.makeGitRepository(target=this_oci_project)
498+ other_oci_project = self.factory.makeOCIProject()
499+ other = self.factory.makeGitRepository(target=other_oci_project)
500+ self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
501+
502+ def test_areRepositoriesMergeable_personal(self):
503+ # Personal repositories are not mergeable into OCI Project
504+ # repositories.
505+ owner = self.factory.makePerson()
506+ oci_project = self.factory.makeOCIProject()
507+ this = self.factory.makeGitRepository(owner=owner, target=oci_project)
508+ other = self.factory.makeGitRepository(owner=owner, target=owner)
509+ self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
510+
511+ def test_areRepositoriesMergeable_project(self):
512+ # Project repositories are not mergeable into OCI Project repositories.
513+ owner = self.factory.makePerson()
514+ oci_project = self.factory.makeOCIProject()
515+ this = self.factory.makeGitRepository(owner=owner, target=oci_project)
516+ project = self.factory.makeProduct()
517+ other = self.factory.makeGitRepository(owner=owner, target=project)
518+ self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
519+
520+ def test_areRepositoriesMergeable_package(self):
521+ # Package repositories are not mergeable into OCI Project repositories.
522+ owner = self.factory.makePerson()
523+ oci_project = self.factory.makeOCIProject()
524+ this = self.factory.makeGitRepository(owner=owner, target=oci_project)
525+ package = self.factory.makeDistributionSourcePackage()
526+ other = self.factory.makeGitRepository(owner=owner, target=package)
527+ self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
528+
529+ def test_collection(self):
530+ # An OCI Project namespace's collection is of
531+ # repositories for the same oci project.
532+ oci_project = self.factory.makeOCIProject()
533+ repositories = [
534+ self.factory.makeGitRepository(target=oci_project)
535+ for _ in range(3)]
536+ self.factory.makeGitRepository(
537+ target=self.factory.makeOCIProject())
538+ self.factory.makeGitRepository(target=self.factory.makeProduct())
539+ self.factory.makeGitRepository(
540+ owner=repositories[0].owner, target=repositories[0].owner)
541+ self.assertContentEqual(
542+ repositories,
543+ repositories[0].namespace.collection.getRepositories())
544+
545+
546 class TestProjectGitNamespacePrivacyWithInformationType(TestCaseWithFactory):
547 """Tests for the privacy aspects of `ProjectGitNamespace`.
548
549@@ -688,6 +812,15 @@ class TestPackageGitNamespace(TestCaseWithFactory, NamespaceMixin):
550 other = self.factory.makeGitRepository(owner=owner, target=project)
551 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
552
553+ def test_areRepositoriesMergeable_oci_project(self):
554+ # OCI Project repositories are not mergeable into package repositories.
555+ owner = self.factory.makePerson()
556+ dsp = self.factory.makeDistributionSourcePackage()
557+ this = self.factory.makeGitRepository(owner=owner, target=dsp)
558+ oci_project = self.factory.makeOCIProject()
559+ other = self.factory.makeGitRepository(owner=owner, target=oci_project)
560+ self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
561+
562 def test_collection(self):
563 # A package namespace's collection is of repositories for the same
564 # package.
565diff --git a/lib/lp/code/model/tests/test_gitrepository.py b/lib/lp/code/model/tests/test_gitrepository.py
566index f1a80ed..1577038 100644
567--- a/lib/lp/code/model/tests/test_gitrepository.py
568+++ b/lib/lp/code/model/tests/test_gitrepository.py
569@@ -222,6 +222,15 @@ class TestGitRepository(TestCaseWithFactory):
570 dsp.sourcepackagename.name, repository.name),
571 repository.unique_name)
572
573+ def test_unique_name_oci_project(self):
574+ oci_project = self.factory.makeOCIProject()
575+ repository = self.factory.makeGitRepository(target=oci_project)
576+ self.assertEqual(
577+ "~%s/%s/+oci/%s/+git/%s" % (
578+ repository.owner.name, oci_project.distribution.name,
579+ oci_project.ociprojectname.name, repository.name),
580+ repository.unique_name)
581+
582 def test_unique_name_personal(self):
583 owner = self.factory.makePerson()
584 repository = self.factory.makeGitRepository(owner=owner, target=owner)
585@@ -239,6 +248,11 @@ class TestGitRepository(TestCaseWithFactory):
586 repository = self.factory.makeGitRepository(target=dsp)
587 self.assertEqual(dsp, repository.target)
588
589+ def test_target_ociproject(self):
590+ oci_project = self.factory.makeOCIProject()
591+ repository = self.factory.makeGitRepository(target=oci_project)
592+ self.assertEqual(oci_project, repository.target)
593+
594 def test_target_personal(self):
595 owner = self.factory.makePerson()
596 repository = self.factory.makeGitRepository(owner=owner, target=owner)
597diff --git a/lib/lp/registry/interfaces/distribution.py b/lib/lp/registry/interfaces/distribution.py
598index 88a0545..c7ddb95 100644
599--- a/lib/lp/registry/interfaces/distribution.py
600+++ b/lib/lp/registry/interfaces/distribution.py
601@@ -500,6 +500,11 @@ class IDistributionPublic(
602 order to create a mirror.
603 """
604
605+ def getOCIProject(name):
606+ """Return a `OCIProject` with the given name for this
607+ distribution, or None.
608+ """
609+
610 @operation_parameters(
611 name=TextLine(title=_("Package name"), required=True))
612 # Really returns IDistributionSourcePackage, see
613diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py
614index 100dccd..64261b9 100644
615--- a/lib/lp/registry/interfaces/ociproject.py
616+++ b/lib/lp/registry/interfaces/ociproject.py
617@@ -15,7 +15,10 @@ from lazr.restful.fields import (
618 CollectionField,
619 Reference,
620 )
621-from zope.interface import Interface
622+from zope.interface import (
623+ Attribute,
624+ Interface,
625+ )
626 from zope.schema import (
627 Datetime,
628 Int,
629@@ -24,6 +27,7 @@ from zope.schema import (
630
631 from lp import _
632 from lp.bugs.interfaces.bugtarget import IBugTarget
633+from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
634 from lp.registry.interfaces.distribution import IDistribution
635 from lp.registry.interfaces.ociprojectname import IOCIProjectName
636 from lp.registry.interfaces.series import SeriesStatus
637@@ -31,7 +35,7 @@ from lp.services.database.constants import DEFAULT
638 from lp.services.fields import PublicPersonChoice
639
640
641-class IOCIProjectView(Interface):
642+class IOCIProjectView(IHasGitRepositories, Interface):
643 """IOCIProject attributes that require launchpad.View permission."""
644
645 id = Int(title=_("ID"), required=True, readonly=True)
646@@ -50,6 +54,8 @@ class IOCIProjectView(Interface):
647 # Really IOCIProjectSeries
648 value_type=Reference(schema=Interface))
649
650+ name = Attribute(_("Name"))
651+
652
653 class IOCIProjectEditableAttributes(IBugTarget):
654 """IOCIProject attributes that can be edited.
655diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
656index 3bdedbe..2288d0e 100644
657--- a/lib/lp/registry/model/distribution.py
658+++ b/lib/lp/registry/model/distribution.py
659@@ -101,6 +101,7 @@ from lp.registry.interfaces.distributionmirror import (
660 MirrorFreshness,
661 MirrorStatus,
662 )
663+from lp.registry.interfaces.ociproject import IOCIProjectSet
664 from lp.registry.interfaces.oopsreferences import IHasOOPSReferences
665 from lp.registry.interfaces.person import (
666 validate_person,
667@@ -826,6 +827,11 @@ class Distribution(SQLBase, BugTargetBase, MakesAnnouncements,
668 name = %s
669 """ % sqlvalues(self.id, name))
670
671+ def getOCIProject(self, name):
672+ oci_project = getUtility(IOCIProjectSet).getByDistributionAndName(
673+ self, name)
674+ return oci_project
675+
676 def getSourcePackage(self, name):
677 """See `IDistribution`."""
678 if ISourcePackageName.providedBy(name):
679diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py
680index 7250692..e718881 100644
681--- a/lib/lp/registry/model/ociproject.py
682+++ b/lib/lp/registry/model/ociproject.py
683@@ -67,6 +67,10 @@ class OCIProject(BugTargetBase, StormBase):
684 name="enable_bugfiling_duplicate_search")
685
686 @property
687+ def name(self):
688+ return self.ociprojectname.name
689+
690+ @property
691 def pillar(self):
692 """See `IBugTarget`."""
693 return self.distribution
694diff --git a/lib/lp/registry/tests/test_distribution.py b/lib/lp/registry/tests/test_distribution.py
695index 8e0b5ba..c7bf86f 100644
696--- a/lib/lp/registry/tests/test_distribution.py
697+++ b/lib/lp/registry/tests/test_distribution.py
698@@ -304,6 +304,14 @@ class TestDistribution(TestCaseWithFactory):
699 InformationType.PUBLIC,
700 distro.getDefaultSpecificationInformationType())
701
702+ def test_getOCIProject(self):
703+ distro = self.factory.makeDistribution()
704+ first_project = self.factory.makeOCIProject(pillar=distro)
705+ # make another project to ensure we don't default
706+ self.factory.makeOCIProject(pillar=distro)
707+ result = distro.getOCIProject(first_project.name)
708+ self.assertEqual(first_project, result)
709+
710
711 class TestDistributionCurrentSourceReleases(
712 CurrentSourceReleasesMixin, TestCase):
713diff --git a/lib/lp/registry/tests/test_ociproject.py b/lib/lp/registry/tests/test_ociproject.py
714index d170bbb..174b12b 100644
715--- a/lib/lp/registry/tests/test_ociproject.py
716+++ b/lib/lp/registry/tests/test_ociproject.py
717@@ -67,6 +67,12 @@ class TestOCIProject(TestCaseWithFactory):
718 oci_project=second_oci_project)
719 self.assertContentEqual([first_series], first_oci_project.series)
720
721+ def test_name(self):
722+ oci_project_name = self.factory.makeOCIProjectName(name='test-name')
723+ oci_project = self.factory.makeOCIProject(
724+ ociprojectname=oci_project_name)
725+ self.assertEqual(oci_project.name, 'test-name')
726+
727
728 class TestOCIProjectSet(TestCaseWithFactory):
729
730diff --git a/lib/lp/security.py b/lib/lp/security.py
731index 21caa71..66c7f73 100644
732--- a/lib/lp/security.py
733+++ b/lib/lp/security.py
734@@ -3434,6 +3434,11 @@ class EditSnapBaseSet(EditByRegistryExpertsOrAdmins):
735 usedfor = ISnapBaseSet
736
737
738+class ViewOCIProject(AnonymousAuthorization):
739+ """Anyone can view an `IOCIProject`."""
740+ usedfor = IOCIProject
741+
742+
743 class EditOCIProject(AuthorizationBase):
744 permission = 'launchpad.Edit'
745 usedfor = IOCIProject

Subscribers

People subscribed via source and target branches

to status/vote changes: