Merge ~cjwatson/launchpad:fix-oci-show-default-git-repository into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 2c16aa5915b122c7c90e23ee434595fa072d0731
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:fix-oci-show-default-git-repository
Merge into: launchpad:master
Diff against target: 169 lines (+78/-2)
4 files modified
lib/lp/code/interfaces/gitnamespace.py (+8/-0)
lib/lp/code/model/gitnamespace.py (+25/-0)
lib/lp/code/xmlrpc/git.py (+4/-2)
lib/lp/code/xmlrpc/tests/test_git.py (+41/-0)
Reviewer Review Type Date Requested Status
Tom Wardill (community) Approve
Review via email: mp+401812@code.launchpad.net

Commit message

Fix creating /pillar/+oci/name via git push

Description of the change

This is a bit more involved than I'd initially anticipated, because OCI projects don't have owners so there isn't an obvious natural choice for the owner of the new repository. For the time being, use the OCI project admin in the case of distribution-based OCI projects, and otherwise the pillar's owner. I expect this may need to evolve further in future.

To post a comment you must log in.
Revision history for this message
Tom Wardill (twom) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/code/interfaces/gitnamespace.py b/lib/lp/code/interfaces/gitnamespace.py
2index ed0a4d3..0e17f08 100644
3--- a/lib/lp/code/interfaces/gitnamespace.py
4+++ b/lib/lp/code/interfaces/gitnamespace.py
5@@ -108,6 +108,14 @@ class IGitNamespacePolicy(Interface):
6 "True iff this namespace permits automatically setting a default "
7 "repository on push.")
8
9+ # XXX cjwatson 2021-04-26: This is a slight hack for the benefit of the
10+ # OCI project namespace. OCI projects don't have owners (recipes do),
11+ # so the usual approach of using the target's owner doesn't work.
12+ default_owner = Attribute(
13+ "The default owner when automatically setting a default repository on "
14+ "push for this namespace, or None if no usable default owner can be "
15+ "determined.")
16+
17 supports_repository_forking = Attribute(
18 "Does this namespace support repository forking at all?")
19
20diff --git a/lib/lp/code/model/gitnamespace.py b/lib/lp/code/model/gitnamespace.py
21index 86169a2..3c2078f 100644
22--- a/lib/lp/code/model/gitnamespace.py
23+++ b/lib/lp/code/model/gitnamespace.py
24@@ -63,6 +63,7 @@ from lp.code.model.branchnamespace import (
25 )
26 from lp.code.model.gitrepository import GitRepository
27 from lp.registry.enums import PersonVisibility
28+from lp.registry.interfaces.distribution import IDistribution
29 from lp.registry.interfaces.distributionsourcepackage import (
30 IDistributionSourcePackage,
31 )
32@@ -285,6 +286,7 @@ class PersonalGitNamespace(_BaseGitNamespace):
33
34 has_defaults = False
35 allow_push_to_set_default = False
36+ default_owner = None
37 supports_merge_proposals = True
38 supports_code_imports = False
39 allow_recipe_name_from_target = False
40@@ -400,6 +402,11 @@ class ProjectGitNamespace(_BaseGitNamespace):
41 repository.sourcepackagename = None
42 repository.oci_project = None
43
44+ @property
45+ def default_owner(self):
46+ """See `IGitNamespacePolicy`."""
47+ return self.target.owner
48+
49 def getAllowedInformationTypes(self, who=None):
50 """See `IGitNamespace`."""
51 # Some policies require that the repository owner or current user
52@@ -497,6 +504,11 @@ class PackageGitNamespace(_BaseGitNamespace):
53 repository.sourcepackagename = dsp.sourcepackagename
54 repository.oci_project = None
55
56+ @property
57+ def default_owner(self):
58+ """See `IGitNamespacePolicy`."""
59+ return self.target.owner
60+
61 def getAllowedInformationTypes(self, who=None):
62 """See `IGitNamespace`."""
63 return PUBLIC_INFORMATION_TYPES
64@@ -586,6 +598,19 @@ class OCIProjectGitNamespace(_BaseGitNamespace):
65 repository.sourcepackagename = None
66 repository.oci_project = self.oci_project
67
68+ @property
69+ def default_owner(self):
70+ """See `IGitNamespacePolicy`."""
71+ # XXX cjwatson 2021-04-26: This may be a bit too restrictive, since
72+ # other users may be able to edit the OCI project. If this becomes
73+ # a problem, it will probably be best to fix it by extending the set
74+ # of people who have launchpad.Edit on repositories in OCI project
75+ # namespaces.
76+ if IDistribution.providedBy(self.target.pillar):
77+ return self.target.pillar.oci_project_admin
78+ else:
79+ return self.target.pillar.owner
80+
81 def getAllowedInformationTypes(self, who=None):
82 """See `IGitNamespace`."""
83 return PUBLIC_INFORMATION_TYPES
84diff --git a/lib/lp/code/xmlrpc/git.py b/lib/lp/code/xmlrpc/git.py
85index 5931e94..21e728d 100644
86--- a/lib/lp/code/xmlrpc/git.py
87+++ b/lib/lp/code/xmlrpc/git.py
88@@ -303,11 +303,13 @@ class GitAPI(LaunchpadXMLRPCView):
89 # split_git_unique_name should have left us without a repository name.
90 assert repository is None
91 if owner is None:
92- if not get_git_namespace(target, None).allow_push_to_set_default:
93+ default_namespace = get_git_namespace(target, None)
94+ if (not default_namespace.allow_push_to_set_default or
95+ default_namespace.default_owner is None):
96 raise GitRepositoryCreationForbidden(
97 "Cannot automatically set the default repository for this "
98 "target; push to a named repository instead.")
99- repository_owner = target.owner
100+ repository_owner = default_namespace.default_owner
101 else:
102 repository_owner = owner
103 namespace = get_git_namespace(target, repository_owner)
104diff --git a/lib/lp/code/xmlrpc/tests/test_git.py b/lib/lp/code/xmlrpc/tests/test_git.py
105index b87b2a3..53a1e23 100644
106--- a/lib/lp/code/xmlrpc/tests/test_git.py
107+++ b/lib/lp/code/xmlrpc/tests/test_git.py
108@@ -573,6 +573,26 @@ class TestGitAPIMixin:
109 self.assertEqual(
110 initial_count, getUtility(IAllGitRepositories).count())
111
112+ def test_translatePath_create_oci_project_not_owner(self):
113+ # Somebody without edit permission on the OCI project cannot create
114+ # a repository and immediately set it as the default for that
115+ # project.
116+ requester = self.factory.makePerson()
117+ distribution = self.factory.makeDistribution(
118+ oci_project_admin=self.factory.makeTeam())
119+ oci_project = self.factory.makeOCIProject(pillar=distribution)
120+ path = u"/%s/+oci/%s" % (oci_project.pillar.name, oci_project.name)
121+ message = "%s is not a member of %s" % (
122+ requester.displayname,
123+ oci_project.pillar.oci_project_admin.displayname)
124+ initial_count = getUtility(IAllGitRepositories).count()
125+ self.assertPermissionDenied(
126+ requester, path, message=message, permission="write")
127+ # No repository was created.
128+ login(ANONYMOUS)
129+ self.assertEqual(
130+ initial_count, getUtility(IAllGitRepositories).count())
131+
132 def test_translatePath_grant_to_other(self):
133 requester = self.factory.makePerson()
134 other_person = self.factory.makePerson()
135@@ -1496,13 +1516,34 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
136 # A repository can be created and immediately set as the default for
137 # an OCI project.
138 requester = self.factory.makePerson()
139+ oci_project_admin = self.factory.makeTeam(members=[requester])
140+ distribution = self.factory.makeDistribution(
141+ oci_project_admin=oci_project_admin)
142+ oci_project = self.factory.makeOCIProject(pillar=distribution)
143+ repository = self.assertCreates(
144+ requester,
145+ u"/%s/+oci/%s" % (oci_project.pillar.name, oci_project.name))
146+ self.assertTrue(repository.target_default)
147+ self.assertTrue(repository.owner_default)
148+ self.assertEqual(oci_project_admin, repository.owner)
149+
150+ def test_translatePath_create_oci_project_default_no_admin(self):
151+ # If the OCI project's distribution has no OCI project admin, then a
152+ # repository cannot (yet) be created and immediately set as the
153+ # default for that OCI project.
154+ requester = self.factory.makePerson()
155 oci_project = self.factory.makeOCIProject()
156 path = u"/%s/+oci/%s" % (oci_project.pillar.name, oci_project.name)
157 message = (
158 "Cannot automatically set the default repository for this target; "
159 "push to a named repository instead.")
160+ initial_count = getUtility(IAllGitRepositories).count()
161 self.assertPermissionDenied(
162 requester, path, message=message, permission="write")
163+ # No repository was created.
164+ login(ANONYMOUS)
165+ self.assertEqual(
166+ initial_count, getUtility(IAllGitRepositories).count())
167
168 def test_translatePath_create_project_owner_default(self):
169 # A repository can be created and immediately set as its owner's

Subscribers

People subscribed via source and target branches

to status/vote changes: