Merge ~twom/launchpad:oci-gitrepository into launchpad:master
- Git
- lp:~twom/launchpad
- oci-gitrepository
- Merge into master
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) |
||||
Related bugs: |
|
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 DistributionSou
Add tests for the model
Add required helper methods to IDistribution
- 55011ed... by Tom Wardill
-
Rename OCIProjectGitNa
mespace to DistributionOCI GitNamespace - 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
Colin Watson (cjwatson) : | # |
Colin Watson (cjwatson) wrote : | # |
Note that the corresponding DB patch is still pending review (https:/
- da868e2... by Tom Wardill
-
Ordering tweaks
- 2c4646f... by Tom Wardill
-
Better AssertionError
- 6e6544c... by Tom Wardill
-
Add more test, inherit in the right place
Colin Watson (cjwatson) : | # |
- 689f5e6... by Tom Wardill
-
Don't export this yet
- 635ea87... by Tom Wardill
-
Remove interface declaration for ociprojectname entirely
Colin Watson (cjwatson) : | # |
- d921f65... by Tom Wardill
-
Fix failing distribution test
Preview Diff
1 | diff --git a/lib/lp/code/configure.zcml b/lib/lp/code/configure.zcml |
2 | index 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"> |
16 | diff --git a/lib/lp/code/interfaces/gitcollection.py b/lib/lp/code/interfaces/gitcollection.py |
17 | index 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 | |
30 | diff --git a/lib/lp/code/interfaces/gitnamespace.py b/lib/lp/code/interfaces/gitnamespace.py |
31 | index 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: |
65 | diff --git a/lib/lp/code/interfaces/gitrepository.py b/lib/lp/code/interfaces/gitrepository.py |
66 | index 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 |
91 | diff --git a/lib/lp/code/model/gitcollection.py b/lib/lp/code/model/gitcollection.py |
92 | index 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( |
130 | diff --git a/lib/lp/code/model/gitnamespace.py b/lib/lp/code/model/gitnamespace.py |
131 | index 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) |
298 | diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py |
299 | index 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 | |
368 | diff --git a/lib/lp/code/model/tests/test_gitcollection.py b/lib/lp/code/model/tests/test_gitcollection.py |
369 | index 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. |
393 | diff --git a/lib/lp/code/model/tests/test_gitnamespace.py b/lib/lp/code/model/tests/test_gitnamespace.py |
394 | index 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. |
565 | diff --git a/lib/lp/code/model/tests/test_gitrepository.py b/lib/lp/code/model/tests/test_gitrepository.py |
566 | index 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) |
597 | diff --git a/lib/lp/registry/interfaces/distribution.py b/lib/lp/registry/interfaces/distribution.py |
598 | index 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 |
613 | diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py |
614 | index 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. |
655 | diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py |
656 | index 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): |
679 | diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py |
680 | index 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 |
694 | diff --git a/lib/lp/registry/tests/test_distribution.py b/lib/lp/registry/tests/test_distribution.py |
695 | index 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): |
713 | diff --git a/lib/lp/registry/tests/test_ociproject.py b/lib/lp/registry/tests/test_ociproject.py |
714 | index 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 | |
730 | diff --git a/lib/lp/security.py b/lib/lp/security.py |
731 | index 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 |
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:
* GenericGitColle ction.preloadDa taForRepositori es needs to preload related OCIProjectName rows. iew.getReposito ryIdentities needs updating. getByUniqueName and IGitRepositoryS et.getByPath. 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. browser. widgets. gitrepositoryta rget will need attention. This can definitely be later, once we're ready for people to create these repositories.
* The third paragraph of the docstring of IGitRepositoryV
* 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.
* lp.code.
* lp.code.