Merge lp:~cjwatson/launchpad/git-sharing into lp:launchpad

Proposed by Colin Watson on 2015-02-16
Status: Merged
Approved by: Colin Watson on 2015-02-20
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
Reviewer Review Type Date Requested Status
William Grant code 2015-02-16 Approve on 2015-02-19
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://code.launchpad.net/~cjwatson/launchpad/git-namespace/+merge/248995 I said that the next branch in this series would bring up test infrastructure. That turns out to have been a slight lie, because I remembered that creating test objects tends to require the ability to set their information type as well, so the sharing infrastructure needs to be in place. This branch sets that up. It's largely straightforward by analogy with branch handling, although I also did a bit of canonicalisation of argument ordering and ensuring that all calls to the methods in question use keyword arguments (oh for Python 3 and keyword-only arguments!).

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.

To post a comment you must log in.
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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