Merge lp:~cjwatson/launchpad/git-sharing into lp:launchpad
- git-sharing
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | no longer in the source branch. |
Merged at revision: | 17355 |
Proposed branch: | lp:~cjwatson/launchpad/git-sharing |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/git-namespace |
Diff against target: |
892 lines (+193/-86) 13 files modified
lib/lp/blueprints/model/specification.py (+2/-2) lib/lp/blueprints/tests/test_specification.py (+4/-4) lib/lp/bugs/model/bug.py (+3/-3) lib/lp/code/browser/branchsubscription.py (+3/-3) lib/lp/code/model/branch.py (+3/-3) lib/lp/code/model/tests/test_branchsubscription.py (+3/-3) lib/lp/registry/browser/pillar.py (+4/-2) lib/lp/registry/interfaces/accesspolicy.py (+2/-1) lib/lp/registry/interfaces/sharingservice.py (+30/-11) lib/lp/registry/model/accesspolicy.py (+16/-6) lib/lp/registry/model/sharingjob.py (+27/-3) lib/lp/registry/services/sharingservice.py (+77/-31) lib/lp/registry/services/tests/test_sharingservice.py (+19/-14) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/git-sharing |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+249816@code.launchpad.net |
This proposal supersedes a proposal from 2015-02-16.
Commit message
Add sharing service support for Git repositories.
Description of the change
In https:/
Even with this, sharing doesn't quite work, but it's almost there. We can put the last bits of it in place once we have IGitCollection.
William Grant (wgrant) : | # |
Preview Diff
1 | === modified file 'lib/lp/blueprints/model/specification.py' |
2 | --- lib/lp/blueprints/model/specification.py 2014-06-19 02:12:50 +0000 |
3 | +++ lib/lp/blueprints/model/specification.py 2015-02-16 13:41:25 +0000 |
4 | @@ -1,4 +1,4 @@ |
5 | -# Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
6 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
7 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | |
9 | __metaclass__ = type |
10 | @@ -758,7 +758,7 @@ |
11 | # Grant the subscriber access if they can't see the |
12 | # specification. |
13 | service = getUtility(IService, 'sharing') |
14 | - ignored, ignored, shared_specs = service.getVisibleArtifacts( |
15 | + _, _, _, shared_specs = service.getVisibleArtifacts( |
16 | person, specifications=[self], ignore_permissions=True) |
17 | if not shared_specs: |
18 | service.ensureAccessGrants( |
19 | |
20 | === modified file 'lib/lp/blueprints/tests/test_specification.py' |
21 | --- lib/lp/blueprints/tests/test_specification.py 2015-01-06 04:52:44 +0000 |
22 | +++ lib/lp/blueprints/tests/test_specification.py 2015-02-16 13:41:25 +0000 |
23 | @@ -1,4 +1,4 @@ |
24 | -# Copyright 2010-2013 Canonical Ltd. This software is licensed under the |
25 | +# Copyright 2010-2015 Canonical Ltd. This software is licensed under the |
26 | # GNU Affero General Public License version 3 (see the file LICENSE). |
27 | |
28 | """Unit tests for Specification.""" |
29 | @@ -490,7 +490,7 @@ |
30 | product=product, information_type=InformationType.PROPRIETARY) |
31 | spec.subscribe(user, subscribed_by=owner) |
32 | service = getUtility(IService, 'sharing') |
33 | - ignored, ignored, shared_specs = service.getVisibleArtifacts( |
34 | + _, _, _, shared_specs = service.getVisibleArtifacts( |
35 | user, specifications=[spec]) |
36 | self.assertEqual([spec], shared_specs) |
37 | # The spec is also returned by getSharedSpecifications(), |
38 | @@ -507,7 +507,7 @@ |
39 | service.sharePillarInformation( |
40 | product, user_2, owner, permissions) |
41 | spec.subscribe(user_2, subscribed_by=owner) |
42 | - ignored, ignored, shared_specs = service.getVisibleArtifacts( |
43 | + _, _, _, shared_specs = service.getVisibleArtifacts( |
44 | user_2, specifications=[spec]) |
45 | self.assertEqual([spec], shared_specs) |
46 | self.assertEqual( |
47 | @@ -527,7 +527,7 @@ |
48 | spec.subscribe(user, subscribed_by=owner) |
49 | spec.unsubscribe(user, unsubscribed_by=owner) |
50 | service = getUtility(IService, 'sharing') |
51 | - ignored, ignored, shared_specs = service.getVisibleArtifacts( |
52 | + _, _, _, shared_specs = service.getVisibleArtifacts( |
53 | user, specifications=[spec]) |
54 | self.assertEqual([], shared_specs) |
55 | |
56 | |
57 | === modified file 'lib/lp/bugs/model/bug.py' |
58 | --- lib/lp/bugs/model/bug.py 2014-11-14 22:10:03 +0000 |
59 | +++ lib/lp/bugs/model/bug.py 2015-02-16 13:41:25 +0000 |
60 | @@ -1,4 +1,4 @@ |
61 | -# Copyright 2009-2014 Canonical Ltd. This software is licensed under the |
62 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
63 | # GNU Affero General Public License version 3 (see the file LICENSE). |
64 | |
65 | """Launchpad bug-related database table classes.""" |
66 | @@ -836,7 +836,7 @@ |
67 | # there is at least one bugtask for which access can be checked. |
68 | if self.default_bugtask: |
69 | service = getUtility(IService, 'sharing') |
70 | - bugs, ignored, ignored = service.getVisibleArtifacts( |
71 | + bugs, _, _, _ = service.getVisibleArtifacts( |
72 | person, bugs=[self], ignore_permissions=True) |
73 | if not bugs: |
74 | service.ensureAccessGrants( |
75 | @@ -1774,7 +1774,7 @@ |
76 | if information_type in PRIVATE_INFORMATION_TYPES: |
77 | service = getUtility(IService, 'sharing') |
78 | for person in (who, self.owner): |
79 | - bugs, ignored, ignored = service.getVisibleArtifacts( |
80 | + bugs, _, _, _ = service.getVisibleArtifacts( |
81 | person, bugs=[self], ignore_permissions=True) |
82 | if not bugs: |
83 | # subscribe() isn't sufficient if a subscription |
84 | |
85 | === modified file 'lib/lp/code/browser/branchsubscription.py' |
86 | --- lib/lp/code/browser/branchsubscription.py 2014-11-28 22:07:05 +0000 |
87 | +++ lib/lp/code/browser/branchsubscription.py 2015-02-16 13:41:25 +0000 |
88 | @@ -1,4 +1,4 @@ |
89 | -# Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
90 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
91 | # GNU Affero General Public License version 3 (see the file LICENSE). |
92 | |
93 | __metaclass__ = type |
94 | @@ -200,7 +200,7 @@ |
95 | page_title = label = "Subscribe to branch" |
96 | |
97 | def validate(self, data): |
98 | - if data.has_key('person'): |
99 | + if 'person' in data: |
100 | person = data['person'] |
101 | subscription = self.context.getSubscription(person) |
102 | if subscription is None and not self.context.userCanBeSubscribed( |
103 | @@ -279,7 +279,7 @@ |
104 | url = canonical_url(self.branch) |
105 | # If the subscriber can no longer see the branch, redirect them away. |
106 | service = getUtility(IService, 'sharing') |
107 | - ignored, branches, ignored = service.getVisibleArtifacts( |
108 | + _, branches, _, _ = service.getVisibleArtifacts( |
109 | self.person, branches=[self.branch], ignore_permissions=True) |
110 | if not branches: |
111 | url = canonical_url(self.branch.target) |
112 | |
113 | === modified file 'lib/lp/code/model/branch.py' |
114 | --- lib/lp/code/model/branch.py 2015-02-16 13:41:24 +0000 |
115 | +++ lib/lp/code/model/branch.py 2015-02-16 13:41:25 +0000 |
116 | @@ -1,4 +1,4 @@ |
117 | -# Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
118 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
119 | # GNU Affero General Public License version 3 (see the file LICENSE). |
120 | |
121 | __metaclass__ = type |
122 | @@ -914,7 +914,7 @@ |
123 | subscription.review_level = code_review_level |
124 | # Grant the subscriber access if they can't see the branch. |
125 | service = getUtility(IService, 'sharing') |
126 | - ignored, branches, ignored = service.getVisibleArtifacts( |
127 | + _, branches, _, _ = service.getVisibleArtifacts( |
128 | person, branches=[self], ignore_permissions=True) |
129 | if not branches: |
130 | service.ensureAccessGrants( |
131 | @@ -928,7 +928,7 @@ |
132 | # currently accessible to the person but which the subscribed_by user |
133 | # has edit permissions for. |
134 | service = getUtility(IService, 'sharing') |
135 | - ignored, invisible_stacked_branches = service.getInvisibleArtifacts( |
136 | + _, invisible_stacked_branches, _ = service.getInvisibleArtifacts( |
137 | person, branches=self.getStackedOnBranches()) |
138 | editable_stacked_on_branches = [ |
139 | branch for branch in invisible_stacked_branches |
140 | |
141 | === modified file 'lib/lp/code/model/tests/test_branchsubscription.py' |
142 | --- lib/lp/code/model/tests/test_branchsubscription.py 2012-09-19 13:22:42 +0000 |
143 | +++ lib/lp/code/model/tests/test_branchsubscription.py 2015-02-16 13:41:25 +0000 |
144 | @@ -1,4 +1,4 @@ |
145 | -# Copyright 2010-2012 Canonical Ltd. This software is licensed under the |
146 | +# Copyright 2010-2015 Canonical Ltd. This software is licensed under the |
147 | # GNU Affero General Public License version 3 (see the file LICENSE). |
148 | |
149 | """Tests for the BranchSubscrptions model object..""" |
150 | @@ -133,7 +133,7 @@ |
151 | None, CodeReviewNotificationLevel.NOEMAIL, owner) |
152 | # The stacked on branch should be visible. |
153 | service = getUtility(IService, 'sharing') |
154 | - ignored, visible_branches, ignored = service.getVisibleArtifacts( |
155 | + _, visible_branches, _, _ = service.getVisibleArtifacts( |
156 | grantee, branches=[private_stacked_on_branch]) |
157 | self.assertContentEqual( |
158 | [private_stacked_on_branch], visible_branches) |
159 | @@ -161,7 +161,7 @@ |
160 | grantee, BranchSubscriptionNotificationLevel.NOEMAIL, |
161 | None, CodeReviewNotificationLevel.NOEMAIL, owner) |
162 | # The stacked on branch should not be visible. |
163 | - ignored, visible_branches, ignored = service.getVisibleArtifacts( |
164 | + _, visible_branches, _, _ = service.getVisibleArtifacts( |
165 | grantee, branches=[private_stacked_on_branch]) |
166 | self.assertContentEqual([], visible_branches) |
167 | self.assertIn( |
168 | |
169 | === modified file 'lib/lp/registry/browser/pillar.py' |
170 | --- lib/lp/registry/browser/pillar.py 2014-11-24 01:20:26 +0000 |
171 | +++ lib/lp/registry/browser/pillar.py 2015-02-16 13:41:25 +0000 |
172 | @@ -1,4 +1,4 @@ |
173 | -# Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
174 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
175 | # GNU Affero General Public License version 3 (see the file LICENSE). |
176 | |
177 | """Common views for objects that implement `IPillar`.""" |
178 | @@ -444,12 +444,14 @@ |
179 | def _loadSharedArtifacts(self): |
180 | # As a concrete can by linked via more than one policy, we use sets to |
181 | # filter out dupes. |
182 | - self.bugtasks, self.branches, self.specifications = ( |
183 | + (self.bugtasks, self.branches, self.gitrepositories, |
184 | + self.specifications) = ( |
185 | self.sharing_service.getSharedArtifacts( |
186 | self.pillar, self.person, self.user)) |
187 | bug_ids = set([bugtask.bug.id for bugtask in self.bugtasks]) |
188 | self.shared_bugs_count = len(bug_ids) |
189 | self.shared_branches_count = len(self.branches) |
190 | + self.shared_gitrepositories_count = len(self.gitrepositories) |
191 | self.shared_specifications_count = len(self.specifications) |
192 | |
193 | def _build_specification_template_data(self, specs, request): |
194 | |
195 | === modified file 'lib/lp/registry/interfaces/accesspolicy.py' |
196 | --- lib/lp/registry/interfaces/accesspolicy.py 2012-09-21 11:41:56 +0000 |
197 | +++ lib/lp/registry/interfaces/accesspolicy.py 2015-02-16 13:41:25 +0000 |
198 | @@ -1,4 +1,4 @@ |
199 | -# Copyright 2011-2012 Canonical Ltd. This software is licensed under the |
200 | +# Copyright 2011-2015 Canonical Ltd. This software is licensed under the |
201 | # GNU Affero General Public License version 3 (see the file LICENSE). |
202 | |
203 | """Interfaces for pillar and artifact access policies.""" |
204 | @@ -35,6 +35,7 @@ |
205 | concrete_artifact = Attribute("Concrete artifact") |
206 | bug_id = Attribute("bug_id") |
207 | branch_id = Attribute("branch_id") |
208 | + gitrepository_id = Attribute("gitrepository_id") |
209 | specification_id = Attribute("specification_id") |
210 | |
211 | |
212 | |
213 | === modified file 'lib/lp/registry/interfaces/sharingservice.py' |
214 | --- lib/lp/registry/interfaces/sharingservice.py 2015-02-06 15:17:07 +0000 |
215 | +++ lib/lp/registry/interfaces/sharingservice.py 2015-02-16 13:41:25 +0000 |
216 | @@ -1,4 +1,4 @@ |
217 | -# Copyright 2012-2013 Canonical Ltd. This software is licensed under the |
218 | +# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
219 | # GNU Affero General Public License version 3 (see the file LICENSE). |
220 | |
221 | """Interfaces for sharing service.""" |
222 | @@ -108,7 +108,7 @@ |
223 | |
224 | :param user: the user making the request. Only artifacts visible to the |
225 | user will be included in the result. |
226 | - :return: a (bugtasks, branches, specifications) tuple |
227 | + :return: a (bugtasks, branches, gitrepositories, specifications) tuple |
228 | """ |
229 | |
230 | def checkPillarArtifactAccess(pillar, user): |
231 | @@ -148,6 +148,14 @@ |
232 | :return: a collection of branches |
233 | """ |
234 | |
235 | + def getSharedGitRepositories(pillar, person, user): |
236 | + """Return the Git repositories shared between the pillar and person. |
237 | + |
238 | + :param user: the user making the request. Only Git repositories |
239 | + visible to the user will be included in the result. |
240 | + :return: a collection of Git repositories. |
241 | + """ |
242 | + |
243 | @export_read_operation() |
244 | @call_with(user=REQUEST_USER) |
245 | @operation_parameters( |
246 | @@ -163,19 +171,25 @@ |
247 | :return: a collection of specifications. |
248 | """ |
249 | |
250 | - def getVisibleArtifacts(person, branches=None, bugs=None): |
251 | + def getVisibleArtifacts(person, bugs=None, branches=None, |
252 | + gitrepositories=None, specifications=None): |
253 | """Return the artifacts shared with person. |
254 | |
255 | Given lists of artifacts, return those a person has access to either |
256 | via a policy grant or artifact grant. |
257 | |
258 | :param person: the person whose access is being checked. |
259 | + :param bugs: the bugs to check for which a person has access. |
260 | :param branches: the branches to check for which a person has access. |
261 | - :param bugs: the bugs to check for which a person has access. |
262 | + :param gitrepositories: the Git repositories to check for which a |
263 | + person has access. |
264 | + :param specifications: the specifications to check for which a |
265 | + person has access. |
266 | :return: a collection of artifacts the person can see. |
267 | """ |
268 | |
269 | - def getInvisibleArtifacts(person, branches=None, bugs=None): |
270 | + def getInvisibleArtifacts(person, bugs=None, branches=None, |
271 | + gitrepositories=None): |
272 | """Return the artifacts which are not shared with person. |
273 | |
274 | Given lists of artifacts, return those a person does not have access to |
275 | @@ -184,8 +198,10 @@ |
276 | access to private information. Internal use only. * |
277 | |
278 | :param person: the person whose access is being checked. |
279 | + :param bugs: the bugs to check for which a person has access. |
280 | :param branches: the branches to check for which a person has access. |
281 | - :param bugs: the bugs to check for which a person has access. |
282 | + :param gitrepositories: the Git repositories to check for which a |
283 | + person has access. |
284 | :return: a collection of artifacts the person can not see. |
285 | """ |
286 | |
287 | @@ -304,10 +320,11 @@ |
288 | branches=List( |
289 | Reference(schema=IBranch), title=_('Branches'), required=False), |
290 | specifications=List( |
291 | - Reference(schema=ISpecification), title=_('Specifications'), required=False)) |
292 | + Reference(schema=ISpecification), title=_('Specifications'), |
293 | + required=False)) |
294 | @operation_for_version('devel') |
295 | - def revokeAccessGrants(pillar, grantee, user, branches=None, bugs=None, |
296 | - specifications=None): |
297 | + def revokeAccessGrants(pillar, grantee, user, bugs=None, branches=None, |
298 | + gitrepositories=None, specifications=None): |
299 | """Remove a grantee's access to the specified artifacts. |
300 | |
301 | :param pillar: the pillar from which to remove access |
302 | @@ -315,6 +332,7 @@ |
303 | :param user: the user making the request |
304 | :param bugs: the bugs for which to revoke access |
305 | :param branches: the branches for which to revoke access |
306 | + :param gitrepositories: the Git repositories for which to revoke access |
307 | :param specifications: the specifications for which to revoke access |
308 | """ |
309 | |
310 | @@ -328,14 +346,15 @@ |
311 | branches=List( |
312 | Reference(schema=IBranch), title=_('Branches'), required=False)) |
313 | @operation_for_version('devel') |
314 | - def ensureAccessGrants(grantees, user, branches=None, bugs=None, |
315 | - specifications=None): |
316 | + def ensureAccessGrants(grantees, user, bugs=None, branches=None, |
317 | + gitrepositories=None, specifications=None): |
318 | """Ensure a grantee has an access grant to the specified artifacts. |
319 | |
320 | :param grantees: the people or teams for whom to grant access |
321 | :param user: the user making the request |
322 | :param bugs: the bugs for which to grant access |
323 | :param branches: the branches for which to grant access |
324 | + :param gitrepositories: the Git repositories for which to grant access |
325 | :param specifications: the specifications for which to grant access |
326 | """ |
327 | |
328 | |
329 | === modified file 'lib/lp/registry/model/accesspolicy.py' |
330 | --- lib/lp/registry/model/accesspolicy.py 2013-06-20 05:50:00 +0000 |
331 | +++ lib/lp/registry/model/accesspolicy.py 2015-02-16 13:41:25 +0000 |
332 | @@ -1,4 +1,4 @@ |
333 | -# Copyright 2011-2012 Canonical Ltd. This software is licensed under the |
334 | +# Copyright 2011-2015 Canonical Ltd. This software is licensed under the |
335 | # GNU Affero General Public License version 3 (see the file LICENSE). |
336 | |
337 | """Model classes for pillar and artifact access policies.""" |
338 | @@ -98,12 +98,16 @@ |
339 | bug = Reference(bug_id, 'Bug.id') |
340 | branch_id = Int(name='branch') |
341 | branch = Reference(branch_id, 'Branch.id') |
342 | + gitrepository_id = Int(name='gitrepository') |
343 | + gitrepository = Reference(gitrepository_id, 'GitRepository.id') |
344 | specification_id = Int(name='specification') |
345 | specification = Reference(specification_id, 'Specification.id') |
346 | |
347 | @property |
348 | def concrete_artifact(self): |
349 | - artifact = self.bug or self.branch or self.specification |
350 | + artifact = ( |
351 | + self.bug or self.branch or self.gitrepository or |
352 | + self.specification) |
353 | return artifact |
354 | |
355 | @classmethod |
356 | @@ -111,10 +115,13 @@ |
357 | from lp.blueprints.interfaces.specification import ISpecification |
358 | from lp.bugs.interfaces.bug import IBug |
359 | from lp.code.interfaces.branch import IBranch |
360 | + from lp.code.interfaces.gitrepository import IGitRepository |
361 | if IBug.providedBy(concrete_artifact): |
362 | col = cls.bug |
363 | elif IBranch.providedBy(concrete_artifact): |
364 | col = cls.branch |
365 | + elif IGitRepository.providedBy(concrete_artifact): |
366 | + col = cls.gitrepository |
367 | elif ISpecification.providedBy(concrete_artifact): |
368 | col = cls.specification |
369 | else: |
370 | @@ -137,6 +144,7 @@ |
371 | from lp.blueprints.interfaces.specification import ISpecification |
372 | from lp.bugs.interfaces.bug import IBug |
373 | from lp.code.interfaces.branch import IBranch |
374 | + from lp.code.interfaces.gitrepository import IGitRepository |
375 | |
376 | existing = list(cls.find(concrete_artifacts)) |
377 | if len(existing) == len(concrete_artifacts): |
378 | @@ -150,15 +158,17 @@ |
379 | insert_values = [] |
380 | for concrete in needed: |
381 | if IBug.providedBy(concrete): |
382 | - insert_values.append((concrete, None, None)) |
383 | + insert_values.append((concrete, None, None, None)) |
384 | elif IBranch.providedBy(concrete): |
385 | - insert_values.append((None, concrete, None)) |
386 | + insert_values.append((None, concrete, None, None)) |
387 | + elif IGitRepository.providedBy(concrete): |
388 | + insert_values.append((None, None, concrete, None)) |
389 | elif ISpecification.providedBy(concrete): |
390 | - insert_values.append((None, None, concrete)) |
391 | + insert_values.append((None, None, None, concrete)) |
392 | else: |
393 | raise ValueError("%r is not a supported artifact" % concrete) |
394 | new = create( |
395 | - (cls.bug, cls.branch, cls.specification), |
396 | + (cls.bug, cls.branch, cls.gitrepository, cls.specification), |
397 | insert_values, get_objects=True) |
398 | return list(existing) + new |
399 | |
400 | |
401 | === modified file 'lib/lp/registry/model/sharingjob.py' |
402 | --- lib/lp/registry/model/sharingjob.py 2013-07-04 08:32:03 +0000 |
403 | +++ lib/lp/registry/model/sharingjob.py 2015-02-16 13:41:25 +0000 |
404 | @@ -1,4 +1,4 @@ |
405 | -# Copyright 2012-2013 Canonical Ltd. This software is licensed under the |
406 | +# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
407 | # GNU Affero General Public License version 3 (see the file LICENSE). |
408 | |
409 | """Job classes related to the sharing feature are in here.""" |
410 | @@ -10,7 +10,6 @@ |
411 | 'RemoveArtifactSubscriptionsJob', |
412 | ] |
413 | |
414 | -import contextlib |
415 | import logging |
416 | |
417 | from lazr.delegates import delegates |
418 | @@ -58,11 +57,13 @@ |
419 | from lp.bugs.model.bugtasksearch import get_bug_privacy_filter_terms |
420 | from lp.code.interfaces.branch import IBranch |
421 | from lp.code.interfaces.branchlookup import IBranchLookup |
422 | +from lp.code.interfaces.gitrepository import IGitRepository |
423 | from lp.code.model.branch import ( |
424 | Branch, |
425 | get_branch_privacy_filter, |
426 | ) |
427 | from lp.code.model.branchsubscription import BranchSubscription |
428 | +from lp.code.model.gitrepository import GitRepository |
429 | from lp.registry.interfaces.person import IPersonSet |
430 | from lp.registry.interfaces.product import IProduct |
431 | from lp.registry.interfaces.sharingjob import ( |
432 | @@ -85,7 +86,6 @@ |
433 | ) |
434 | from lp.services.job.runner import BaseRunnableJob |
435 | from lp.services.mail.sendmail import format_address_for_person |
436 | -from lp.services.webapp import errorlog |
437 | |
438 | |
439 | class SharingJobType(DBEnumeratedType): |
440 | @@ -265,6 +265,7 @@ |
441 | |
442 | bug_ids = [] |
443 | branch_ids = [] |
444 | + gitrepository_ids = [] |
445 | specification_ids = [] |
446 | if artifacts: |
447 | for artifact in artifacts: |
448 | @@ -272,6 +273,8 @@ |
449 | bug_ids.append(artifact.id) |
450 | elif IBranch.providedBy(artifact): |
451 | branch_ids.append(artifact.id) |
452 | + elif IGitRepository.providedBy(artifact): |
453 | + gitrepository_ids.append(artifact.id) |
454 | elif ISpecification.providedBy(artifact): |
455 | specification_ids.append(artifact.id) |
456 | else: |
457 | @@ -283,6 +286,7 @@ |
458 | metadata = { |
459 | 'bug_ids': bug_ids, |
460 | 'branch_ids': branch_ids, |
461 | + 'gitrepository_ids': gitrepository_ids, |
462 | 'specification_ids': specification_ids, |
463 | 'information_types': information_types, |
464 | 'requestor.id': requestor.id |
465 | @@ -315,6 +319,10 @@ |
466 | return [getUtility(IBranchLookup).get(id) for id in self.branch_ids] |
467 | |
468 | @property |
469 | + def gitrepository_ids(self): |
470 | + return self.metadata.get('gitrepository_ids', []) |
471 | + |
472 | + @property |
473 | def specification_ids(self): |
474 | return self.metadata.get('specification_ids', []) |
475 | |
476 | @@ -343,6 +351,7 @@ |
477 | 'requestor': self.requestor.name, |
478 | 'bug_ids': self.bug_ids, |
479 | 'branch_ids': self.branch_ids, |
480 | + 'gitrepository_ids': self.gitrepository_ids, |
481 | 'specification_ids': self.specification_ids, |
482 | 'pillar': getattr(self.pillar, 'name', None), |
483 | 'grantee': getattr(self.grantee, 'name', None) |
484 | @@ -358,10 +367,14 @@ |
485 | |
486 | bug_filters = [] |
487 | branch_filters = [] |
488 | + gitrepository_filters = [] |
489 | specification_filters = [] |
490 | |
491 | if self.branch_ids: |
492 | branch_filters.append(Branch.id.is_in(self.branch_ids)) |
493 | + if self.gitrepository_ids: |
494 | + gitrepository_filters.append(GitRepository.id.is_in( |
495 | + self.gitrepository_ids)) |
496 | if self.specification_ids: |
497 | specification_filters.append(Specification.id.is_in( |
498 | self.specification_ids)) |
499 | @@ -374,6 +387,9 @@ |
500 | self.information_types)) |
501 | branch_filters.append( |
502 | Branch.information_type.is_in(self.information_types)) |
503 | + gitrepository_filters.append( |
504 | + GitRepository.information_type.is_in( |
505 | + self.information_types)) |
506 | specification_filters.append( |
507 | Specification.information_type.is_in( |
508 | self.information_types)) |
509 | @@ -381,12 +397,16 @@ |
510 | bug_filters.append( |
511 | BugTaskFlat.product == self.product) |
512 | branch_filters.append(Branch.product == self.product) |
513 | + gitrepository_filters.append( |
514 | + GitRepository.project == self.product) |
515 | specification_filters.append( |
516 | Specification.product == self.product) |
517 | if self.distro: |
518 | bug_filters.append( |
519 | BugTaskFlat.distribution == self.distro) |
520 | branch_filters.append(Branch.distribution == self.distro) |
521 | + gitrepository_filters.append( |
522 | + GitRepository.distribution == self.distro) |
523 | specification_filters.append( |
524 | Specification.distribution == self.distro) |
525 | |
526 | @@ -401,6 +421,8 @@ |
527 | Select( |
528 | TeamParticipation.personID, |
529 | where=TeamParticipation.team == self.grantee))) |
530 | + # XXX cjwatson 2015-02-05: Fill this in once we have |
531 | + # GitRepositorySubscription. |
532 | specification_filters.append( |
533 | In(SpecificationSubscription.personID, |
534 | Select( |
535 | @@ -430,6 +452,8 @@ |
536 | for sub in branch_subscriptions: |
537 | sub.branch.unsubscribe( |
538 | sub.person, self.requestor, ignore_permissions=True) |
539 | + # XXX cjwatson 2015-02-05: Fill this in once we have |
540 | + # GitRepositorySubscription. |
541 | if specification_filters: |
542 | specification_filters.append(Not(*get_specification_privacy_filter( |
543 | SpecificationSubscription.personID))) |
544 | |
545 | === modified file 'lib/lp/registry/services/sharingservice.py' |
546 | --- lib/lp/registry/services/sharingservice.py 2013-06-20 05:50:00 +0000 |
547 | +++ lib/lp/registry/services/sharingservice.py 2015-02-16 13:41:25 +0000 |
548 | @@ -1,4 +1,4 @@ |
549 | -# Copyright 2012-2013 Canonical Ltd. This software is licensed under the |
550 | +# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
551 | # GNU Affero General Public License version 3 (see the file LICENSE). |
552 | |
553 | """Classes for pillar and artifact sharing service.""" |
554 | @@ -194,10 +194,12 @@ |
555 | |
556 | @available_with_permission('launchpad.Driver', 'pillar') |
557 | def getSharedArtifacts(self, pillar, person, user, include_bugs=True, |
558 | - include_branches=True, include_specifications=True): |
559 | + include_branches=True, include_gitrepositories=True, |
560 | + include_specifications=True): |
561 | """See `ISharingService`.""" |
562 | bug_ids = set() |
563 | branch_ids = set() |
564 | + gitrepository_ids = set() |
565 | specification_ids = set() |
566 | for artifact in self.getArtifactGrantsForPersonOnPillar( |
567 | pillar, person): |
568 | @@ -205,6 +207,8 @@ |
569 | bug_ids.add(artifact.bug_id) |
570 | elif artifact.branch_id and include_branches: |
571 | branch_ids.add(artifact.branch_id) |
572 | + elif artifact.gitrepository_id and include_gitrepositories: |
573 | + gitrepository_ids.add(artifact.gitrepository_id) |
574 | elif artifact.specification_id and include_specifications: |
575 | specification_ids.add(artifact.specification_id) |
576 | |
577 | @@ -221,11 +225,14 @@ |
578 | wanted_branches = all_branches.visibleByUser(user).withIds( |
579 | *branch_ids) |
580 | branches = list(wanted_branches.getBranches()) |
581 | + # Load the Git repositories. |
582 | + gitrepositories = [] |
583 | + # XXX cjwatson 2015-02-16: Fill in once IGitCollection is in place. |
584 | specifications = [] |
585 | if specification_ids: |
586 | specifications = load(Specification, specification_ids) |
587 | |
588 | - return bugtasks, branches, specifications |
589 | + return bugtasks, branches, gitrepositories, specifications |
590 | |
591 | def checkPillarArtifactAccess(self, pillar, user): |
592 | """See `ISharingService`.""" |
593 | @@ -245,25 +252,33 @@ |
594 | @available_with_permission('launchpad.Driver', 'pillar') |
595 | def getSharedBugs(self, pillar, person, user): |
596 | """See `ISharingService`.""" |
597 | - bugtasks, ignore, ignore = self.getSharedArtifacts( |
598 | + bugtasks, _, _, _ = self.getSharedArtifacts( |
599 | pillar, person, user, include_branches=False, |
600 | - include_specifications=False) |
601 | + include_gitrepositories=False, include_specifications=False) |
602 | return bugtasks |
603 | |
604 | @available_with_permission('launchpad.Driver', 'pillar') |
605 | def getSharedBranches(self, pillar, person, user): |
606 | """See `ISharingService`.""" |
607 | - ignore, branches, ignore = self.getSharedArtifacts( |
608 | + _, branches, _, _ = self.getSharedArtifacts( |
609 | pillar, person, user, include_bugs=False, |
610 | - include_specifications=False) |
611 | + include_gitrepositories=False, include_specifications=False) |
612 | return branches |
613 | |
614 | @available_with_permission('launchpad.Driver', 'pillar') |
615 | + def getSharedGitRepositories(self, pillar, person, user): |
616 | + """See `ISharingService`.""" |
617 | + _, _, gitrepositories, _ = self.getSharedArtifacts( |
618 | + pillar, person, user, include_bugs=False, include_branches=False, |
619 | + include_specifications=False) |
620 | + return gitrepositories |
621 | + |
622 | + @available_with_permission('launchpad.Driver', 'pillar') |
623 | def getSharedSpecifications(self, pillar, person, user): |
624 | """See `ISharingService`.""" |
625 | - ignore, ignore, specifications = self.getSharedArtifacts( |
626 | - pillar, person, user, include_bugs=False, |
627 | - include_branches=False) |
628 | + _, _, _, specifications = self.getSharedArtifacts( |
629 | + pillar, person, user, include_bugs=False, include_branches=False, |
630 | + include_gitrepositories=False) |
631 | return specifications |
632 | |
633 | def _getVisiblePrivateSpecificationIDs(self, person, specifications): |
634 | @@ -300,11 +315,13 @@ |
635 | TeamParticipation.personID == person.id, |
636 | In(Specification.id, spec_ids))) |
637 | |
638 | - def getVisibleArtifacts(self, person, branches=None, bugs=None, |
639 | - specifications=None, ignore_permissions=False): |
640 | + def getVisibleArtifacts(self, person, bugs=None, branches=None, |
641 | + gitrepositories=None, specifications=None, |
642 | + ignore_permissions=False): |
643 | """See `ISharingService`.""" |
644 | bugs_by_id = {} |
645 | branches_by_id = {} |
646 | + gitrepositories_by_id = {} |
647 | for bug in bugs or []: |
648 | if (not ignore_permissions |
649 | and not check_permission('launchpad.View', bug)): |
650 | @@ -315,6 +332,11 @@ |
651 | and not check_permission('launchpad.View', branch)): |
652 | raise Unauthorized |
653 | branches_by_id[branch.id] = branch |
654 | + for gitrepository in gitrepositories or []: |
655 | + if (not ignore_permissions |
656 | + and not check_permission('launchpad.View', gitrepository)): |
657 | + raise Unauthorized |
658 | + gitrepositories_by_id[gitrepository.id] = gitrepository |
659 | for spec in specifications or []: |
660 | if (not ignore_permissions |
661 | and not check_permission('launchpad.View', spec)): |
662 | @@ -336,6 +358,11 @@ |
663 | *branches_by_id.keys()) |
664 | visible_branches = list(wanted_branches.getBranches()) |
665 | |
666 | + # Load the Git repositories. |
667 | + visible_gitrepositories = [] |
668 | + # XXX cjwatson 2015-02-16: Fill in once IGitCollection is in place. |
669 | + |
670 | + # Load the specifications. |
671 | visible_specs = [] |
672 | if specifications: |
673 | visible_private_spec_ids = self._getVisiblePrivateSpecificationIDs( |
674 | @@ -344,16 +371,22 @@ |
675 | spec for spec in specifications |
676 | if spec.id in visible_private_spec_ids or not spec.private] |
677 | |
678 | - return visible_bugs, visible_branches, visible_specs |
679 | + return ( |
680 | + visible_bugs, visible_branches, visible_gitrepositories, |
681 | + visible_specs) |
682 | |
683 | - def getInvisibleArtifacts(self, person, branches=None, bugs=None): |
684 | + def getInvisibleArtifacts(self, person, bugs=None, branches=None, |
685 | + gitrepositories=None): |
686 | """See `ISharingService`.""" |
687 | bugs_by_id = {} |
688 | branches_by_id = {} |
689 | + gitrepositories_by_id = {} |
690 | for bug in bugs or []: |
691 | bugs_by_id[bug.id] = bug |
692 | for branch in branches or []: |
693 | branches_by_id[branch.id] = branch |
694 | + for gitrepository in gitrepositories or []: |
695 | + gitrepositories_by_id[gitrepository.id] = gitrepository |
696 | |
697 | # Load the bugs. |
698 | visible_bug_ids = set() |
699 | @@ -376,7 +409,11 @@ |
700 | branches_by_id[branch_id] |
701 | for branch_id in invisible_branch_ids] |
702 | |
703 | - return invisible_bugs, invisible_branches |
704 | + # Load the Git repositories. |
705 | + invisible_gitrepositories = [] |
706 | + # XXX cjwatson 2015-02-16: Fill in once IGitCollection is in place. |
707 | + |
708 | + return invisible_bugs, invisible_branches, invisible_gitrepositories |
709 | |
710 | def getPeopleWithoutAccess(self, concrete_artifact, people): |
711 | """See `ISharingService`.""" |
712 | @@ -722,42 +759,51 @@ |
713 | return invisible_types |
714 | |
715 | @available_with_permission('launchpad.Edit', 'pillar') |
716 | - def revokeAccessGrants(self, pillar, grantee, user, branches=None, |
717 | - bugs=None, specifications=None): |
718 | + def revokeAccessGrants(self, pillar, grantee, user, bugs=None, |
719 | + branches=None, gitrepositories=None, |
720 | + specifications=None): |
721 | """See `ISharingService`.""" |
722 | |
723 | - if not branches and not bugs and not specifications: |
724 | + if (not bugs and not branches and not gitrepositories and |
725 | + not specifications): |
726 | raise ValueError( |
727 | - "Either bugs, branches or specifications must be specified") |
728 | + "Either bugs, branches, gitrepositories, or specifications " |
729 | + "must be specified") |
730 | |
731 | artifacts = [] |
732 | + if bugs: |
733 | + artifacts.extend(bugs) |
734 | if branches: |
735 | artifacts.extend(branches) |
736 | - if bugs: |
737 | - artifacts.extend(bugs) |
738 | + if gitrepositories: |
739 | + artifacts.extend(gitrepositories) |
740 | if specifications: |
741 | artifacts.extend(specifications) |
742 | - # Find the access artifacts associated with the bugs and branches. |
743 | + # Find the access artifacts associated with the bugs, branches, Git |
744 | + # repositories, and specifications. |
745 | accessartifact_source = getUtility(IAccessArtifactSource) |
746 | artifacts_to_delete = accessartifact_source.find(artifacts) |
747 | - # Revoke access to bugs/branches for the specified grantee. |
748 | + # Revoke access to artifacts for the specified grantee. |
749 | getUtility(IAccessArtifactGrantSource).revokeByArtifact( |
750 | artifacts_to_delete, [grantee]) |
751 | |
752 | # Create a job to remove subscriptions for artifacts the grantee can no |
753 | # longer see. |
754 | - getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
755 | + return getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
756 | user, artifacts, grantee=grantee, pillar=pillar) |
757 | |
758 | - def ensureAccessGrants(self, grantees, user, branches=None, bugs=None, |
759 | - specifications=None, ignore_permissions=False): |
760 | + def ensureAccessGrants(self, grantees, user, bugs=None, branches=None, |
761 | + gitrepositories=None, specifications=None, |
762 | + ignore_permissions=False): |
763 | """See `ISharingService`.""" |
764 | |
765 | artifacts = [] |
766 | + if bugs: |
767 | + artifacts.extend(bugs) |
768 | if branches: |
769 | artifacts.extend(branches) |
770 | - if bugs: |
771 | - artifacts.extend(bugs) |
772 | + if gitrepositories: |
773 | + artifacts.extend(gitrepositories) |
774 | if specifications: |
775 | artifacts.extend(specifications) |
776 | if not ignore_permissions: |
777 | @@ -767,15 +813,15 @@ |
778 | if not check_permission('launchpad.Edit', artifact): |
779 | raise Unauthorized |
780 | |
781 | - # Ensure there are access artifacts associated with the bugs and |
782 | - # branches. |
783 | + # Ensure there are access artifacts associated with the bugs, |
784 | + # branches, Git repositories, and specifications. |
785 | artifacts = getUtility(IAccessArtifactSource).ensure(artifacts) |
786 | aagsource = getUtility(IAccessArtifactGrantSource) |
787 | artifacts_with_grants = [ |
788 | artifact_grant.abstract_artifact |
789 | for artifact_grant in |
790 | aagsource.find(product(artifacts, grantees))] |
791 | - # Create access to bugs/branches for the specified grantee for which a |
792 | + # Create access to artifacts for the specified grantee for which a |
793 | # grant does not already exist. |
794 | missing_artifacts = set(artifacts) - set(artifacts_with_grants) |
795 | getUtility(IAccessArtifactGrantSource).grant( |
796 | |
797 | === modified file 'lib/lp/registry/services/tests/test_sharingservice.py' |
798 | --- lib/lp/registry/services/tests/test_sharingservice.py 2015-02-06 15:17:07 +0000 |
799 | +++ lib/lp/registry/services/tests/test_sharingservice.py 2015-02-16 13:41:25 +0000 |
800 | @@ -1,4 +1,4 @@ |
801 | -# Copyright 2012-2013 Canonical Ltd. This software is licensed under the |
802 | +# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
803 | # GNU Affero General Public License version 3 (see the file LICENSE). |
804 | |
805 | __metaclass__ = type |
806 | @@ -1075,9 +1075,10 @@ |
807 | |
808 | # Check that grantees have expected access grants and subscriptions. |
809 | for person in [team_grantee, person_grantee]: |
810 | - visible_bugs, visible_branches, visible_specs = ( |
811 | + visible_bugs, visible_branches, _, visible_specs = ( |
812 | self.service.getVisibleArtifacts( |
813 | - person, branches, bugs, specifications)) |
814 | + person, bugs=bugs, branches=branches, |
815 | + specifications=specifications)) |
816 | self.assertContentEqual(bugs or [], visible_bugs) |
817 | self.assertContentEqual(branches or [], visible_branches) |
818 | self.assertContentEqual(specifications or [], visible_specs) |
819 | @@ -1102,8 +1103,9 @@ |
820 | for person in [team_grantee, person_grantee]: |
821 | for bug in bugs or []: |
822 | self.assertNotIn(person, bug.getDirectSubscribers()) |
823 | - visible_bugs, visible_branches, visible_specs = ( |
824 | - self.service.getVisibleArtifacts(person, branches, bugs)) |
825 | + visible_bugs, visible_branches, _, visible_specs = ( |
826 | + self.service.getVisibleArtifacts( |
827 | + person, bugs=bugs, branches=branches)) |
828 | self.assertContentEqual([], visible_bugs) |
829 | self.assertContentEqual([], visible_branches) |
830 | self.assertContentEqual([], visible_specs) |
831 | @@ -1386,7 +1388,7 @@ |
832 | product, grantee, user) |
833 | |
834 | # Check the results. |
835 | - shared_bugtasks, shared_branches, shared_specs = ( |
836 | + shared_bugtasks, shared_branches, _, shared_specs = ( |
837 | self.service.getSharedArtifacts(product, grantee, user)) |
838 | self.assertContentEqual(bug_tasks[:9], shared_bugtasks) |
839 | self.assertContentEqual(branches[:9], shared_branches) |
840 | @@ -1673,8 +1675,9 @@ |
841 | # Test the getVisibleArtifacts method. |
842 | grantee, ignore, branches, bugs, specs = self._make_Artifacts() |
843 | # Check the results. |
844 | - shared_bugs, shared_branches, shared_specs = ( |
845 | - self.service.getVisibleArtifacts(grantee, branches, bugs, specs)) |
846 | + shared_bugs, shared_branches, _, shared_specs = ( |
847 | + self.service.getVisibleArtifacts( |
848 | + grantee, bugs=bugs, branches=branches, specifications=specs)) |
849 | self.assertContentEqual(bugs[:5], shared_bugs) |
850 | self.assertContentEqual(branches[:5], shared_branches) |
851 | self.assertContentEqual(specs[:5], shared_specs) |
852 | @@ -1683,8 +1686,9 @@ |
853 | # getVisibleArtifacts() returns private specifications if |
854 | # user has a policy grant for the pillar of the specification. |
855 | ignore, owner, branches, bugs, specs = self._make_Artifacts() |
856 | - shared_bugs, shared_branches, shared_specs = ( |
857 | - self.service.getVisibleArtifacts(owner, branches, bugs, specs)) |
858 | + shared_bugs, shared_branches, _, shared_specs = ( |
859 | + self.service.getVisibleArtifacts( |
860 | + owner, bugs=bugs, branches=branches, specifications=specs)) |
861 | self.assertContentEqual(bugs, shared_bugs) |
862 | self.assertContentEqual(branches, shared_branches) |
863 | self.assertContentEqual(specs, shared_specs) |
864 | @@ -1693,8 +1697,9 @@ |
865 | # Test the getInvisibleArtifacts method. |
866 | grantee, ignore, branches, bugs, specs = self._make_Artifacts() |
867 | # Check the results. |
868 | - not_shared_bugs, not_shared_branches = ( |
869 | - self.service.getInvisibleArtifacts(grantee, branches, bugs)) |
870 | + not_shared_bugs, not_shared_branches, _ = ( |
871 | + self.service.getInvisibleArtifacts( |
872 | + grantee, bugs=bugs, branches=branches)) |
873 | self.assertContentEqual(bugs[5:], not_shared_bugs) |
874 | self.assertContentEqual(branches[5:], not_shared_branches) |
875 | |
876 | @@ -1718,7 +1723,7 @@ |
877 | information_type=InformationType.USERDATA) |
878 | bugs.append(bug) |
879 | |
880 | - shared_bugs, shared_branches, shared_specs = ( |
881 | + shared_bugs, shared_branches, _, shared_specs = ( |
882 | self.service.getVisibleArtifacts(grantee, bugs=bugs)) |
883 | self.assertContentEqual(bugs, shared_bugs) |
884 | |
885 | @@ -1726,7 +1731,7 @@ |
886 | for x in range(0, 5): |
887 | change_callback(bugs[x], owner) |
888 | # Check the results. |
889 | - shared_bugs, shared_branches, shared_specs = ( |
890 | + shared_bugs, shared_branches, _, shared_specs = ( |
891 | self.service.getVisibleArtifacts(grantee, bugs=bugs)) |
892 | self.assertContentEqual(bugs[5:], shared_bugs) |
893 |