Merge lp:~cjwatson/launchpad/git-webservice into lp:launchpad
- git-webservice
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 17384 |
Proposed branch: | lp:~cjwatson/launchpad/git-webservice |
Merge into: | lp:launchpad |
Diff against target: |
795 lines (+388/-47) 10 files modified
lib/lp/app/browser/launchpad.py (+2/-0) lib/lp/code/browser/configure.zcml (+5/-0) lib/lp/code/configure.zcml (+2/-1) lib/lp/code/interfaces/gitrepository.py (+133/-37) lib/lp/code/interfaces/hasgitrepositories.py (+4/-0) lib/lp/code/interfaces/webservice.py (+8/-0) lib/lp/code/model/tests/test_gitrepository.py (+176/-8) lib/lp/registry/interfaces/sharingservice.py (+18/-1) lib/lp/registry/services/tests/test_sharingservice.py (+17/-0) lib/lp/services/webservice/wadl-to-refhtml.xsl (+23/-0) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/git-webservice |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+251978@code.launchpad.net |
Commit message
Export Git-related methods on the webservice.
Description of the change
Export Git-related methods on the webservice.
This is reasonably basic, but also fairly complete with respect to the operations that exist so far. The main thing that's not exported is IGitRepositoryS
To post a comment you must log in.
Revision history for this message
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/app/browser/launchpad.py' | |||
2 | --- lib/lp/app/browser/launchpad.py 2014-11-27 11:01:16 +0000 | |||
3 | +++ lib/lp/app/browser/launchpad.py 2015-03-06 16:33:15 +0000 | |||
4 | @@ -102,6 +102,7 @@ | |||
5 | 102 | from lp.code.interfaces.branchlookup import IBranchLookup | 102 | from lp.code.interfaces.branchlookup import IBranchLookup |
6 | 103 | from lp.code.interfaces.codehosting import IBazaarApplication | 103 | from lp.code.interfaces.codehosting import IBazaarApplication |
7 | 104 | from lp.code.interfaces.codeimport import ICodeImportSet | 104 | from lp.code.interfaces.codeimport import ICodeImportSet |
8 | 105 | from lp.code.interfaces.gitrepository import IGitRepositorySet | ||
9 | 105 | from lp.hardwaredb.interfaces.hwdb import IHWDBApplication | 106 | from lp.hardwaredb.interfaces.hwdb import IHWDBApplication |
10 | 106 | from lp.layers import WebServiceLayer | 107 | from lp.layers import WebServiceLayer |
11 | 107 | from lp.registry.interfaces.announcement import IAnnouncementSet | 108 | from lp.registry.interfaces.announcement import IAnnouncementSet |
12 | @@ -783,6 +784,7 @@ | |||
13 | 783 | 'codeofconduct': ICodeOfConductSet, | 784 | 'codeofconduct': ICodeOfConductSet, |
14 | 784 | '+countries': ICountrySet, | 785 | '+countries': ICountrySet, |
15 | 785 | 'distros': IDistributionSet, | 786 | 'distros': IDistributionSet, |
16 | 787 | '+git': IGitRepositorySet, | ||
17 | 786 | '+hwdb': IHWDBApplication, | 788 | '+hwdb': IHWDBApplication, |
18 | 787 | 'karmaaction': IKarmaActionSet, | 789 | 'karmaaction': IKarmaActionSet, |
19 | 788 | '+imports': ITranslationImportQueue, | 790 | '+imports': ITranslationImportQueue, |
20 | 789 | 791 | ||
21 | === modified file 'lib/lp/code/browser/configure.zcml' | |||
22 | --- lib/lp/code/browser/configure.zcml 2015-03-04 16:56:48 +0000 | |||
23 | +++ lib/lp/code/browser/configure.zcml 2015-03-06 16:33:15 +0000 | |||
24 | @@ -13,6 +13,11 @@ | |||
25 | 13 | path_expression="string:branches" | 13 | path_expression="string:branches" |
26 | 14 | parent_utility="lp.services.webapp.interfaces.ILaunchpadRoot" | 14 | parent_utility="lp.services.webapp.interfaces.ILaunchpadRoot" |
27 | 15 | /> | 15 | /> |
28 | 16 | <browser:url | ||
29 | 17 | for="lp.code.interfaces.gitrepository.IGitRepositorySet" | ||
30 | 18 | path_expression="string:+git" | ||
31 | 19 | parent_utility="lp.services.webapp.interfaces.ILaunchpadRoot" | ||
32 | 20 | /> | ||
33 | 16 | <browser:feeds | 21 | <browser:feeds |
34 | 17 | module="lp.code.feed.branch" | 22 | module="lp.code.feed.branch" |
35 | 18 | classes="BranchFeed PersonBranchFeed ProductBranchFeed ProjectBranchFeed | 23 | classes="BranchFeed PersonBranchFeed ProductBranchFeed ProjectBranchFeed |
36 | 19 | 24 | ||
37 | === modified file 'lib/lp/code/configure.zcml' | |||
38 | --- lib/lp/code/configure.zcml 2015-02-26 17:20:31 +0000 | |||
39 | +++ lib/lp/code/configure.zcml 2015-03-06 16:33:15 +0000 | |||
40 | @@ -818,7 +818,8 @@ | |||
41 | 818 | <require | 818 | <require |
42 | 819 | permission="launchpad.Moderate" | 819 | permission="launchpad.Moderate" |
43 | 820 | interface="lp.code.interfaces.gitrepository.IGitRepositoryModerate" | 820 | interface="lp.code.interfaces.gitrepository.IGitRepositoryModerate" |
45 | 821 | set_schema="lp.code.interfaces.gitrepository.IGitRepositoryModerateAttributes" /> | 821 | set_schema="lp.code.interfaces.gitrepository.IGitRepositoryModerateAttributes" |
46 | 822 | set_attributes="date_last_modified" /> | ||
47 | 822 | <require | 823 | <require |
48 | 823 | permission="launchpad.Edit" | 824 | permission="launchpad.Edit" |
49 | 824 | interface="lp.code.interfaces.gitrepository.IGitRepositoryEdit" /> | 825 | interface="lp.code.interfaces.gitrepository.IGitRepositoryEdit" /> |
50 | 825 | 826 | ||
51 | === modified file 'lib/lp/code/interfaces/gitrepository.py' | |||
52 | --- lib/lp/code/interfaces/gitrepository.py 2015-03-05 14:13:16 +0000 | |||
53 | +++ lib/lp/code/interfaces/gitrepository.py 2015-03-06 16:33:15 +0000 | |||
54 | @@ -17,7 +17,24 @@ | |||
55 | 17 | 17 | ||
56 | 18 | import re | 18 | import re |
57 | 19 | 19 | ||
58 | 20 | from lazr.restful.declarations import ( | ||
59 | 21 | call_with, | ||
60 | 22 | collection_default_content, | ||
61 | 23 | export_as_webservice_collection, | ||
62 | 24 | export_as_webservice_entry, | ||
63 | 25 | export_destructor_operation, | ||
64 | 26 | export_read_operation, | ||
65 | 27 | export_write_operation, | ||
66 | 28 | exported, | ||
67 | 29 | mutator_for, | ||
68 | 30 | operation_for_version, | ||
69 | 31 | operation_parameters, | ||
70 | 32 | operation_returns_collection_of, | ||
71 | 33 | operation_returns_entry, | ||
72 | 34 | REQUEST_USER, | ||
73 | 35 | ) | ||
74 | 20 | from lazr.restful.fields import Reference | 36 | from lazr.restful.fields import Reference |
75 | 37 | from lazr.restful.interface import copy_field | ||
76 | 21 | from zope.component import getUtility | 38 | from zope.component import getUtility |
77 | 22 | from zope.interface import ( | 39 | from zope.interface import ( |
78 | 23 | Attribute, | 40 | Attribute, |
79 | @@ -40,6 +57,7 @@ | |||
80 | 40 | from lp.registry.interfaces.distributionsourcepackage import ( | 57 | from lp.registry.interfaces.distributionsourcepackage import ( |
81 | 41 | IDistributionSourcePackage, | 58 | IDistributionSourcePackage, |
82 | 42 | ) | 59 | ) |
83 | 60 | from lp.registry.interfaces.person import IPerson | ||
84 | 43 | from lp.registry.interfaces.persondistributionsourcepackage import ( | 61 | from lp.registry.interfaces.persondistributionsourcepackage import ( |
85 | 44 | IPersonDistributionSourcePackageFactory, | 62 | IPersonDistributionSourcePackageFactory, |
86 | 45 | ) | 63 | ) |
87 | @@ -97,68 +115,76 @@ | |||
88 | 97 | 115 | ||
89 | 98 | id = Int(title=_("ID"), readonly=True, required=True) | 116 | id = Int(title=_("ID"), readonly=True, required=True) |
90 | 99 | 117 | ||
98 | 100 | date_created = Datetime( | 118 | date_created = exported(Datetime( |
99 | 101 | title=_("Date created"), required=True, readonly=True) | 119 | title=_("Date created"), required=True, readonly=True)) |
100 | 102 | 120 | ||
101 | 103 | date_last_modified = Datetime( | 121 | registrant = exported(PublicPersonChoice( |
95 | 104 | title=_("Date last modified"), required=True, readonly=True) | ||
96 | 105 | |||
97 | 106 | registrant = PublicPersonChoice( | ||
102 | 107 | title=_("Registrant"), required=True, readonly=True, | 122 | title=_("Registrant"), required=True, readonly=True, |
103 | 108 | vocabulary="ValidPersonOrTeam", | 123 | vocabulary="ValidPersonOrTeam", |
105 | 109 | description=_("The person who registered this Git repository.")) | 124 | description=_("The person who registered this Git repository."))) |
106 | 110 | 125 | ||
109 | 111 | owner = PersonChoice( | 126 | owner = exported(PersonChoice( |
110 | 112 | title=_("Owner"), required=True, readonly=False, | 127 | title=_("Owner"), required=True, readonly=True, |
111 | 113 | vocabulary="AllUserTeamsParticipationPlusSelf", | 128 | vocabulary="AllUserTeamsParticipationPlusSelf", |
112 | 114 | description=_( | 129 | description=_( |
113 | 115 | "The owner of this Git repository. This controls who can modify " | 130 | "The owner of this Git repository. This controls who can modify " |
115 | 116 | "the repository.")) | 131 | "the repository."))) |
116 | 117 | 132 | ||
121 | 118 | target = Reference( | 133 | target = exported( |
122 | 119 | title=_("Target"), required=True, readonly=True, | 134 | Reference( |
123 | 120 | schema=IHasGitRepositories, | 135 | title=_("Target"), required=True, readonly=True, |
124 | 121 | description=_("The target of the repository.")) | 136 | schema=IHasGitRepositories, |
125 | 137 | description=_("The target of the repository.")), | ||
126 | 138 | as_of="devel") | ||
127 | 122 | 139 | ||
128 | 123 | namespace = Attribute( | 140 | namespace = Attribute( |
129 | 124 | "The namespace of this repository, as an `IGitNamespace`.") | 141 | "The namespace of this repository, as an `IGitNamespace`.") |
130 | 125 | 142 | ||
132 | 126 | information_type = Choice( | 143 | # XXX cjwatson 2015-01-29: Add some advice about default repository |
133 | 144 | # naming. | ||
134 | 145 | name = exported(TextLine( | ||
135 | 146 | title=_("Name"), required=True, readonly=True, | ||
136 | 147 | constraint=git_repository_name_validator, | ||
137 | 148 | description=_( | ||
138 | 149 | "The repository name. Keep very short, unique, and descriptive, " | ||
139 | 150 | "because it will be used in URLs."))) | ||
140 | 151 | |||
141 | 152 | information_type = exported(Choice( | ||
142 | 127 | title=_("Information type"), vocabulary=InformationType, | 153 | title=_("Information type"), vocabulary=InformationType, |
143 | 128 | required=True, readonly=True, default=InformationType.PUBLIC, | 154 | required=True, readonly=True, default=InformationType.PUBLIC, |
144 | 129 | description=_( | 155 | description=_( |
146 | 130 | "The type of information contained in this repository.")) | 156 | "The type of information contained in this repository."))) |
147 | 131 | 157 | ||
149 | 132 | owner_default = Bool( | 158 | owner_default = exported(Bool( |
150 | 133 | title=_("Owner default"), required=True, readonly=True, | 159 | title=_("Owner default"), required=True, readonly=True, |
151 | 134 | description=_( | 160 | description=_( |
152 | 135 | "Whether this repository is the default for its owner and " | 161 | "Whether this repository is the default for its owner and " |
154 | 136 | "target.")) | 162 | "target."))) |
155 | 137 | 163 | ||
157 | 138 | target_default = Bool( | 164 | target_default = exported(Bool( |
158 | 139 | title=_("Target default"), required=True, readonly=True, | 165 | title=_("Target default"), required=True, readonly=True, |
159 | 140 | description=_( | 166 | description=_( |
161 | 141 | "Whether this repository is the default for its target.")) | 167 | "Whether this repository is the default for its target."))) |
162 | 142 | 168 | ||
164 | 143 | unique_name = Text( | 169 | unique_name = exported(Text( |
165 | 144 | title=_("Unique name"), readonly=True, | 170 | title=_("Unique name"), readonly=True, |
166 | 145 | description=_( | 171 | description=_( |
167 | 146 | "Unique name of the repository, including the owner and project " | 172 | "Unique name of the repository, including the owner and project " |
169 | 147 | "names.")) | 173 | "names."))) |
170 | 148 | 174 | ||
172 | 149 | display_name = Text( | 175 | display_name = exported(Text( |
173 | 150 | title=_("Display name"), readonly=True, | 176 | title=_("Display name"), readonly=True, |
175 | 151 | description=_("Display name of the repository.")) | 177 | description=_("Display name of the repository."))) |
176 | 152 | 178 | ||
177 | 153 | shortened_path = Attribute( | 179 | shortened_path = Attribute( |
178 | 154 | "The shortest reasonable version of the path to this repository.") | 180 | "The shortest reasonable version of the path to this repository.") |
179 | 155 | 181 | ||
181 | 156 | git_identity = Text( | 182 | git_identity = exported(Text( |
182 | 157 | title=_("Git identity"), readonly=True, | 183 | title=_("Git identity"), readonly=True, |
183 | 158 | description=_( | 184 | description=_( |
184 | 159 | "If this is the default repository for some target, then this is " | 185 | "If this is the default repository for some target, then this is " |
185 | 160 | "'lp:' plus a shortcut version of the path via that target. " | 186 | "'lp:' plus a shortcut version of the path via that target. " |
187 | 161 | "Otherwise it is simply 'lp:' plus the unique name.")) | 187 | "Otherwise it is simply 'lp:' plus the unique name."))) |
188 | 162 | 188 | ||
189 | 163 | def setOwnerDefault(value): | 189 | def setOwnerDefault(value): |
190 | 164 | """Set whether this repository is the default for its owner-target. | 190 | """Set whether this repository is the default for its owner-target. |
191 | @@ -242,19 +268,20 @@ | |||
192 | 242 | """IGitRepository attributes that can be edited by more than one community. | 268 | """IGitRepository attributes that can be edited by more than one community. |
193 | 243 | """ | 269 | """ |
194 | 244 | 270 | ||
203 | 245 | # XXX cjwatson 2015-01-29: Add some advice about default repository | 271 | date_last_modified = exported(Datetime( |
204 | 246 | # naming. | 272 | title=_("Date last modified"), required=True, readonly=True)) |
197 | 247 | name = TextLine( | ||
198 | 248 | title=_("Name"), required=True, | ||
199 | 249 | constraint=git_repository_name_validator, | ||
200 | 250 | description=_( | ||
201 | 251 | "The repository name. Keep very short, unique, and descriptive, " | ||
202 | 252 | "because it will be used in URLs.")) | ||
205 | 253 | 273 | ||
206 | 254 | 274 | ||
207 | 255 | class IGitRepositoryModerate(Interface): | 275 | class IGitRepositoryModerate(Interface): |
208 | 256 | """IGitRepository methods that can be called by more than one community.""" | 276 | """IGitRepository methods that can be called by more than one community.""" |
209 | 257 | 277 | ||
210 | 278 | @mutator_for(IGitRepositoryView["information_type"]) | ||
211 | 279 | @operation_parameters( | ||
212 | 280 | information_type=copy_field(IGitRepositoryView["information_type"]), | ||
213 | 281 | ) | ||
214 | 282 | @call_with(user=REQUEST_USER) | ||
215 | 283 | @export_write_operation() | ||
216 | 284 | @operation_for_version("devel") | ||
217 | 258 | def transitionToInformationType(information_type, user, | 285 | def transitionToInformationType(information_type, user, |
218 | 259 | verify_policy=True): | 286 | verify_policy=True): |
219 | 260 | """Set the information type for this repository. | 287 | """Set the information type for this repository. |
220 | @@ -269,12 +296,31 @@ | |||
221 | 269 | class IGitRepositoryEdit(Interface): | 296 | class IGitRepositoryEdit(Interface): |
222 | 270 | """IGitRepository methods that require launchpad.Edit permission.""" | 297 | """IGitRepository methods that require launchpad.Edit permission.""" |
223 | 271 | 298 | ||
224 | 299 | @mutator_for(IGitRepositoryView["owner"]) | ||
225 | 300 | @call_with(user=REQUEST_USER) | ||
226 | 301 | @operation_parameters( | ||
227 | 302 | new_owner=Reference( | ||
228 | 303 | title=_("The new owner of the repository."), schema=IPerson)) | ||
229 | 304 | @export_write_operation() | ||
230 | 305 | @operation_for_version("devel") | ||
231 | 272 | def setOwner(new_owner, user): | 306 | def setOwner(new_owner, user): |
232 | 273 | """Set the owner of the repository to be `new_owner`.""" | 307 | """Set the owner of the repository to be `new_owner`.""" |
233 | 274 | 308 | ||
234 | 309 | @mutator_for(IGitRepositoryView["target"]) | ||
235 | 310 | @call_with(user=REQUEST_USER) | ||
236 | 311 | @operation_parameters( | ||
237 | 312 | target=Reference( | ||
238 | 313 | title=_( | ||
239 | 314 | "The project, distribution source package, or person the " | ||
240 | 315 | "repository belongs to."), | ||
241 | 316 | schema=IHasGitRepositories, required=True)) | ||
242 | 317 | @export_write_operation() | ||
243 | 318 | @operation_for_version("devel") | ||
244 | 275 | def setTarget(target, user): | 319 | def setTarget(target, user): |
245 | 276 | """Set the target of the repository.""" | 320 | """Set the target of the repository.""" |
246 | 277 | 321 | ||
247 | 322 | @export_destructor_operation() | ||
248 | 323 | @operation_for_version("devel") | ||
249 | 278 | def destroySelf(): | 324 | def destroySelf(): |
250 | 279 | """Delete the specified repository.""" | 325 | """Delete the specified repository.""" |
251 | 280 | 326 | ||
252 | @@ -283,14 +329,22 @@ | |||
253 | 283 | IGitRepositoryModerate, IGitRepositoryEdit): | 329 | IGitRepositoryModerate, IGitRepositoryEdit): |
254 | 284 | """A Git repository.""" | 330 | """A Git repository.""" |
255 | 285 | 331 | ||
257 | 286 | private = Bool( | 332 | # Mark repositories as exported entries for the Launchpad API. |
258 | 333 | # XXX cjwatson 2015-01-19 bug=760849: "beta" is a lie to get WADL | ||
259 | 334 | # generation working. Individual attributes must set their version to | ||
260 | 335 | # "devel". | ||
261 | 336 | export_as_webservice_entry(plural_name="git_repositories", as_of="beta") | ||
262 | 337 | |||
263 | 338 | private = exported(Bool( | ||
264 | 287 | title=_("Private"), required=False, readonly=True, | 339 | title=_("Private"), required=False, readonly=True, |
266 | 288 | description=_("This repository is visible only to its subscribers.")) | 340 | description=_("This repository is visible only to its subscribers."))) |
267 | 289 | 341 | ||
268 | 290 | 342 | ||
269 | 291 | class IGitRepositorySet(Interface): | 343 | class IGitRepositorySet(Interface): |
270 | 292 | """Interface representing the set of Git repositories.""" | 344 | """Interface representing the set of Git repositories.""" |
271 | 293 | 345 | ||
272 | 346 | export_as_webservice_collection(IGitRepository) | ||
273 | 347 | |||
274 | 294 | def new(registrant, owner, target, name, information_type=None, | 348 | def new(registrant, owner, target, name, information_type=None, |
275 | 295 | date_created=None): | 349 | date_created=None): |
276 | 296 | """Create a Git repository and return it. | 350 | """Create a Git repository and return it. |
277 | @@ -306,6 +360,12 @@ | |||
278 | 306 | """ | 360 | """ |
279 | 307 | 361 | ||
280 | 308 | # Marker for references to Git URL layouts: ##GITNAMESPACE## | 362 | # Marker for references to Git URL layouts: ##GITNAMESPACE## |
281 | 363 | @call_with(user=REQUEST_USER) | ||
282 | 364 | @operation_parameters( | ||
283 | 365 | path=TextLine(title=_("Repository path"), required=True)) | ||
284 | 366 | @operation_returns_entry(IGitRepository) | ||
285 | 367 | @export_read_operation() | ||
286 | 368 | @operation_for_version("devel") | ||
287 | 309 | def getByPath(user, path): | 369 | def getByPath(user, path): |
288 | 310 | """Find a repository by its path. | 370 | """Find a repository by its path. |
289 | 311 | 371 | ||
290 | @@ -324,6 +384,13 @@ | |||
291 | 324 | Return None if no match was found. | 384 | Return None if no match was found. |
292 | 325 | """ | 385 | """ |
293 | 326 | 386 | ||
294 | 387 | @call_with(user=REQUEST_USER) | ||
295 | 388 | @operation_parameters( | ||
296 | 389 | target=Reference( | ||
297 | 390 | title=_("Target"), required=True, schema=IHasGitRepositories)) | ||
298 | 391 | @operation_returns_collection_of(IGitRepository) | ||
299 | 392 | @export_read_operation() | ||
300 | 393 | @operation_for_version("devel") | ||
301 | 327 | def getRepositories(user, target): | 394 | def getRepositories(user, target): |
302 | 328 | """Get all repositories for a target. | 395 | """Get all repositories for a target. |
303 | 329 | 396 | ||
304 | @@ -334,6 +401,12 @@ | |||
305 | 334 | :return: A collection of `IGitRepository` objects. | 401 | :return: A collection of `IGitRepository` objects. |
306 | 335 | """ | 402 | """ |
307 | 336 | 403 | ||
308 | 404 | @operation_parameters( | ||
309 | 405 | target=Reference( | ||
310 | 406 | title=_("Target"), required=True, schema=IHasGitRepositories)) | ||
311 | 407 | @operation_returns_entry(IGitRepository) | ||
312 | 408 | @export_read_operation() | ||
313 | 409 | @operation_for_version("devel") | ||
314 | 337 | def getDefaultRepository(target): | 410 | def getDefaultRepository(target): |
315 | 338 | """Get the default repository for a target. | 411 | """Get the default repository for a target. |
316 | 339 | 412 | ||
317 | @@ -343,6 +416,13 @@ | |||
318 | 343 | :return: An `IGitRepository`, or None. | 416 | :return: An `IGitRepository`, or None. |
319 | 344 | """ | 417 | """ |
320 | 345 | 418 | ||
321 | 419 | @operation_parameters( | ||
322 | 420 | owner=Reference(title=_("Owner"), required=True, schema=IPerson), | ||
323 | 421 | target=Reference( | ||
324 | 422 | title=_("Target"), required=True, schema=IHasGitRepositories)) | ||
325 | 423 | @operation_returns_entry(IGitRepository) | ||
326 | 424 | @export_read_operation() | ||
327 | 425 | @operation_for_version("devel") | ||
328 | 346 | def getDefaultRepositoryForOwner(owner, target): | 426 | def getDefaultRepositoryForOwner(owner, target): |
329 | 347 | """Get a person's default repository for a target. | 427 | """Get a person's default repository for a target. |
330 | 348 | 428 | ||
331 | @@ -353,6 +433,13 @@ | |||
332 | 353 | :return: An `IGitRepository`, or None. | 433 | :return: An `IGitRepository`, or None. |
333 | 354 | """ | 434 | """ |
334 | 355 | 435 | ||
335 | 436 | @operation_parameters( | ||
336 | 437 | target=Reference( | ||
337 | 438 | title=_("Target"), required=True, schema=IHasGitRepositories), | ||
338 | 439 | repository=Reference( | ||
339 | 440 | title=_("Git repository"), required=False, schema=IGitRepository)) | ||
340 | 441 | @export_write_operation() | ||
341 | 442 | @operation_for_version("devel") | ||
342 | 356 | def setDefaultRepository(target, repository): | 443 | def setDefaultRepository(target, repository): |
343 | 357 | """Set the default repository for a target. | 444 | """Set the default repository for a target. |
344 | 358 | 445 | ||
345 | @@ -363,6 +450,14 @@ | |||
346 | 363 | :raises GitTargetError: if `target` is an `IPerson`. | 450 | :raises GitTargetError: if `target` is an `IPerson`. |
347 | 364 | """ | 451 | """ |
348 | 365 | 452 | ||
349 | 453 | @operation_parameters( | ||
350 | 454 | owner=Reference(title=_("Owner"), required=True, schema=IPerson), | ||
351 | 455 | target=Reference( | ||
352 | 456 | title=_("Target"), required=True, schema=IHasGitRepositories), | ||
353 | 457 | repository=Reference( | ||
354 | 458 | title=_("Git repository"), required=False, schema=IGitRepository)) | ||
355 | 459 | @export_write_operation() | ||
356 | 460 | @operation_for_version("devel") | ||
357 | 366 | def setDefaultRepositoryForOwner(owner, target, repository): | 461 | def setDefaultRepositoryForOwner(owner, target, repository): |
358 | 367 | """Set a person's default repository for a target. | 462 | """Set a person's default repository for a target. |
359 | 368 | 463 | ||
360 | @@ -374,6 +469,7 @@ | |||
361 | 374 | :raises GitTargetError: if `target` is an `IPerson`. | 469 | :raises GitTargetError: if `target` is an `IPerson`. |
362 | 375 | """ | 470 | """ |
363 | 376 | 471 | ||
364 | 472 | @collection_default_content() | ||
365 | 377 | def empty_list(): | 473 | def empty_list(): |
366 | 378 | """Return an empty collection of repositories. | 474 | """Return an empty collection of repositories. |
367 | 379 | 475 | ||
368 | 380 | 476 | ||
369 | === modified file 'lib/lp/code/interfaces/hasgitrepositories.py' | |||
370 | --- lib/lp/code/interfaces/hasgitrepositories.py 2015-03-04 19:05:47 +0000 | |||
371 | +++ lib/lp/code/interfaces/hasgitrepositories.py 2015-03-06 16:33:15 +0000 | |||
372 | @@ -9,6 +9,7 @@ | |||
373 | 9 | 'IHasGitRepositories', | 9 | 'IHasGitRepositories', |
374 | 10 | ] | 10 | ] |
375 | 11 | 11 | ||
376 | 12 | from lazr.restful.declarations import export_as_webservice_entry | ||
377 | 12 | from zope.interface import Interface | 13 | from zope.interface import Interface |
378 | 13 | 14 | ||
379 | 14 | 15 | ||
380 | @@ -18,3 +19,6 @@ | |||
381 | 18 | A project contains Git repositories, a source package on a distribution | 19 | A project contains Git repositories, a source package on a distribution |
382 | 19 | contains branches, and a person contains "personal" branches. | 20 | contains branches, and a person contains "personal" branches. |
383 | 20 | """ | 21 | """ |
384 | 22 | |||
385 | 23 | export_as_webservice_entry( | ||
386 | 24 | singular_name="git_target", plural_name="git_targets", as_of="devel") | ||
387 | 21 | 25 | ||
388 | === modified file 'lib/lp/code/interfaces/webservice.py' | |||
389 | --- lib/lp/code/interfaces/webservice.py 2015-01-30 18:24:07 +0000 | |||
390 | +++ lib/lp/code/interfaces/webservice.py 2015-03-06 16:33:15 +0000 | |||
391 | @@ -25,6 +25,9 @@ | |||
392 | 25 | 'ICodeReviewComment', | 25 | 'ICodeReviewComment', |
393 | 26 | 'ICodeReviewVoteReference', | 26 | 'ICodeReviewVoteReference', |
394 | 27 | 'IDiff', | 27 | 'IDiff', |
395 | 28 | 'IGitRepository', | ||
396 | 29 | 'IGitRepositorySet', | ||
397 | 30 | 'IHasGitRepositories', | ||
398 | 28 | 'IPreviewDiff', | 31 | 'IPreviewDiff', |
399 | 29 | 'ISourcePackageRecipe', | 32 | 'ISourcePackageRecipe', |
400 | 30 | 'ISourcePackageRecipeBuild', | 33 | 'ISourcePackageRecipeBuild', |
401 | @@ -57,6 +60,11 @@ | |||
402 | 57 | IDiff, | 60 | IDiff, |
403 | 58 | IPreviewDiff, | 61 | IPreviewDiff, |
404 | 59 | ) | 62 | ) |
405 | 63 | from lp.code.interfaces.gitrepository import ( | ||
406 | 64 | IGitRepository, | ||
407 | 65 | IGitRepositorySet, | ||
408 | 66 | ) | ||
409 | 67 | from lp.code.interfaces.hasgitrepositories import IHasGitRepositories | ||
410 | 60 | from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe | 68 | from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe |
411 | 61 | from lp.code.interfaces.sourcepackagerecipebuild import ( | 69 | from lp.code.interfaces.sourcepackagerecipebuild import ( |
412 | 62 | ISourcePackageRecipeBuild, | 70 | ISourcePackageRecipeBuild, |
413 | 63 | 71 | ||
414 | === modified file 'lib/lp/code/model/tests/test_gitrepository.py' | |||
415 | --- lib/lp/code/model/tests/test_gitrepository.py 2015-03-05 14:13:16 +0000 | |||
416 | +++ lib/lp/code/model/tests/test_gitrepository.py 2015-03-06 16:33:15 +0000 | |||
417 | @@ -7,6 +7,7 @@ | |||
418 | 7 | 7 | ||
419 | 8 | from datetime import datetime | 8 | from datetime import datetime |
420 | 9 | from functools import partial | 9 | from functools import partial |
421 | 10 | import json | ||
422 | 10 | 11 | ||
423 | 11 | from lazr.lifecycle.event import ObjectModifiedEvent | 12 | from lazr.lifecycle.event import ObjectModifiedEvent |
424 | 12 | import pytz | 13 | import pytz |
425 | @@ -54,8 +55,11 @@ | |||
426 | 54 | from lp.services.database.constants import UTC_NOW | 55 | from lp.services.database.constants import UTC_NOW |
427 | 55 | from lp.services.features.testing import FeatureFixture | 56 | from lp.services.features.testing import FeatureFixture |
428 | 56 | from lp.services.webapp.authorization import check_permission | 57 | from lp.services.webapp.authorization import check_permission |
429 | 58 | from lp.services.webapp.interfaces import OAuthPermission | ||
430 | 57 | from lp.testing import ( | 59 | from lp.testing import ( |
431 | 58 | admin_logged_in, | 60 | admin_logged_in, |
432 | 61 | ANONYMOUS, | ||
433 | 62 | api_url, | ||
434 | 59 | celebrity_logged_in, | 63 | celebrity_logged_in, |
435 | 60 | person_logged_in, | 64 | person_logged_in, |
436 | 61 | TestCaseWithFactory, | 65 | TestCaseWithFactory, |
437 | @@ -65,6 +69,7 @@ | |||
438 | 65 | DatabaseFunctionalLayer, | 69 | DatabaseFunctionalLayer, |
439 | 66 | ZopelessDatabaseLayer, | 70 | ZopelessDatabaseLayer, |
440 | 67 | ) | 71 | ) |
441 | 72 | from lp.testing.pages import webservice_for_person | ||
442 | 68 | 73 | ||
443 | 69 | 74 | ||
444 | 70 | class TestGitRepositoryFeatureFlag(TestCaseWithFactory): | 75 | class TestGitRepositoryFeatureFlag(TestCaseWithFactory): |
445 | @@ -476,14 +481,6 @@ | |||
446 | 476 | self.assertEqual( | 481 | self.assertEqual( |
447 | 477 | InformationType.PRIVATESECURITY, repository.information_type) | 482 | InformationType.PRIVATESECURITY, repository.information_type) |
448 | 478 | 483 | ||
449 | 479 | def test_attribute_smoketest(self): | ||
450 | 480 | # Users with launchpad.Moderate can set attributes. | ||
451 | 481 | project = self.factory.makeProduct() | ||
452 | 482 | repository = self.factory.makeGitRepository(target=project) | ||
453 | 483 | with person_logged_in(project.owner): | ||
454 | 484 | repository.name = u"not-secret" | ||
455 | 485 | self.assertEqual(u"not-secret", repository.name) | ||
456 | 486 | |||
457 | 487 | 484 | ||
458 | 488 | class TestGitRepositorySetOwner(TestCaseWithFactory): | 485 | class TestGitRepositorySetOwner(TestCaseWithFactory): |
459 | 489 | """Test `IGitRepository.setOwner`.""" | 486 | """Test `IGitRepository.setOwner`.""" |
460 | @@ -858,3 +855,174 @@ | |||
461 | 858 | TestGitRepositorySetDefaultsOwnerMixin, | 855 | TestGitRepositorySetDefaultsOwnerMixin, |
462 | 859 | TestGitRepositorySetDefaultsPackage): | 856 | TestGitRepositorySetDefaultsPackage): |
463 | 860 | pass | 857 | pass |
464 | 858 | |||
465 | 859 | |||
466 | 860 | class TestGitRepositoryWebservice(TestCaseWithFactory): | ||
467 | 861 | """Tests for the webservice.""" | ||
468 | 862 | |||
469 | 863 | layer = DatabaseFunctionalLayer | ||
470 | 864 | |||
471 | 865 | def setUp(self): | ||
472 | 866 | super(TestGitRepositoryWebservice, self).setUp() | ||
473 | 867 | self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"})) | ||
474 | 868 | |||
475 | 869 | def test_getRepositories_project(self): | ||
476 | 870 | project_db = self.factory.makeProduct() | ||
477 | 871 | repository_db = self.factory.makeGitRepository(target=project_db) | ||
478 | 872 | webservice = webservice_for_person( | ||
479 | 873 | repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC) | ||
480 | 874 | webservice.default_api_version = "devel" | ||
481 | 875 | with person_logged_in(ANONYMOUS): | ||
482 | 876 | repository_url = api_url(repository_db) | ||
483 | 877 | owner_url = api_url(repository_db.owner) | ||
484 | 878 | project_url = api_url(project_db) | ||
485 | 879 | response = webservice.named_get( | ||
486 | 880 | "/+git", "getRepositories", user=owner_url, target=project_url) | ||
487 | 881 | self.assertEqual(200, response.status) | ||
488 | 882 | self.assertEqual( | ||
489 | 883 | [webservice.getAbsoluteUrl(repository_url)], | ||
490 | 884 | [entry["self_link"] for entry in response.jsonBody()["entries"]]) | ||
491 | 885 | |||
492 | 886 | def test_getRepositories_package(self): | ||
493 | 887 | dsp_db = self.factory.makeDistributionSourcePackage() | ||
494 | 888 | repository_db = self.factory.makeGitRepository(target=dsp_db) | ||
495 | 889 | webservice = webservice_for_person( | ||
496 | 890 | repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC) | ||
497 | 891 | webservice.default_api_version = "devel" | ||
498 | 892 | with person_logged_in(ANONYMOUS): | ||
499 | 893 | repository_url = api_url(repository_db) | ||
500 | 894 | owner_url = api_url(repository_db.owner) | ||
501 | 895 | dsp_url = api_url(dsp_db) | ||
502 | 896 | response = webservice.named_get( | ||
503 | 897 | "/+git", "getRepositories", user=owner_url, target=dsp_url) | ||
504 | 898 | self.assertEqual(200, response.status) | ||
505 | 899 | self.assertEqual( | ||
506 | 900 | [webservice.getAbsoluteUrl(repository_url)], | ||
507 | 901 | [entry["self_link"] for entry in response.jsonBody()["entries"]]) | ||
508 | 902 | |||
509 | 903 | def test_getRepositories_personal(self): | ||
510 | 904 | owner_db = self.factory.makePerson() | ||
511 | 905 | repository_db = self.factory.makeGitRepository( | ||
512 | 906 | owner=owner_db, target=owner_db) | ||
513 | 907 | webservice = webservice_for_person( | ||
514 | 908 | owner_db, permission=OAuthPermission.WRITE_PUBLIC) | ||
515 | 909 | webservice.default_api_version = "devel" | ||
516 | 910 | with person_logged_in(ANONYMOUS): | ||
517 | 911 | repository_url = api_url(repository_db) | ||
518 | 912 | owner_url = api_url(owner_db) | ||
519 | 913 | response = webservice.named_get( | ||
520 | 914 | "/+git", "getRepositories", user=owner_url, target=owner_url) | ||
521 | 915 | self.assertEqual(200, response.status) | ||
522 | 916 | self.assertEqual( | ||
523 | 917 | [webservice.getAbsoluteUrl(repository_url)], | ||
524 | 918 | [entry["self_link"] for entry in response.jsonBody()["entries"]]) | ||
525 | 919 | |||
526 | 920 | def test_set_information_type(self): | ||
527 | 921 | # The repository owner can change the information type. | ||
528 | 922 | repository_db = self.factory.makeGitRepository() | ||
529 | 923 | webservice = webservice_for_person( | ||
530 | 924 | repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC) | ||
531 | 925 | webservice.default_api_version = "devel" | ||
532 | 926 | with person_logged_in(ANONYMOUS): | ||
533 | 927 | repository_url = api_url(repository_db) | ||
534 | 928 | response = webservice.patch( | ||
535 | 929 | repository_url, "application/json", | ||
536 | 930 | json.dumps({"information_type": "Public Security"})) | ||
537 | 931 | self.assertEqual(209, response.status) | ||
538 | 932 | with person_logged_in(ANONYMOUS): | ||
539 | 933 | self.assertEqual( | ||
540 | 934 | InformationType.PUBLICSECURITY, repository_db.information_type) | ||
541 | 935 | |||
542 | 936 | def test_set_information_type_other_person(self): | ||
543 | 937 | # An unrelated user cannot change the information type. | ||
544 | 938 | repository_db = self.factory.makeGitRepository() | ||
545 | 939 | webservice = webservice_for_person( | ||
546 | 940 | self.factory.makePerson(), permission=OAuthPermission.WRITE_PUBLIC) | ||
547 | 941 | webservice.default_api_version = "devel" | ||
548 | 942 | with person_logged_in(ANONYMOUS): | ||
549 | 943 | repository_url = api_url(repository_db) | ||
550 | 944 | response = webservice.patch( | ||
551 | 945 | repository_url, "application/json", | ||
552 | 946 | json.dumps({"information_type": "Public Security"})) | ||
553 | 947 | self.assertEqual(401, response.status) | ||
554 | 948 | with person_logged_in(ANONYMOUS): | ||
555 | 949 | self.assertEqual( | ||
556 | 950 | InformationType.PUBLIC, repository_db.information_type) | ||
557 | 951 | |||
558 | 952 | def test_set_target(self): | ||
559 | 953 | # The repository owner can move the repository to another target; | ||
560 | 954 | # this redirects to the new location. | ||
561 | 955 | repository_db = self.factory.makeGitRepository() | ||
562 | 956 | new_project_db = self.factory.makeProduct() | ||
563 | 957 | webservice = webservice_for_person( | ||
564 | 958 | repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC) | ||
565 | 959 | webservice.default_api_version = "devel" | ||
566 | 960 | with person_logged_in(ANONYMOUS): | ||
567 | 961 | repository_url = api_url(repository_db) | ||
568 | 962 | new_project_url = api_url(new_project_db) | ||
569 | 963 | response = webservice.patch( | ||
570 | 964 | repository_url, "application/json", | ||
571 | 965 | json.dumps({"target_link": new_project_url})) | ||
572 | 966 | self.assertEqual(301, response.status) | ||
573 | 967 | with person_logged_in(ANONYMOUS): | ||
574 | 968 | self.assertEqual( | ||
575 | 969 | webservice.getAbsoluteUrl(api_url(repository_db)), | ||
576 | 970 | response.getHeader("Location")) | ||
577 | 971 | self.assertEqual(new_project_db, repository_db.target) | ||
578 | 972 | |||
579 | 973 | def test_set_target_other_person(self): | ||
580 | 974 | # An unrelated person cannot change the target. | ||
581 | 975 | project_db = self.factory.makeProduct() | ||
582 | 976 | repository_db = self.factory.makeGitRepository(target=project_db) | ||
583 | 977 | new_project_db = self.factory.makeProduct() | ||
584 | 978 | webservice = webservice_for_person( | ||
585 | 979 | self.factory.makePerson(), permission=OAuthPermission.WRITE_PUBLIC) | ||
586 | 980 | webservice.default_api_version = "devel" | ||
587 | 981 | with person_logged_in(ANONYMOUS): | ||
588 | 982 | repository_url = api_url(repository_db) | ||
589 | 983 | new_project_url = api_url(new_project_db) | ||
590 | 984 | response = webservice.patch( | ||
591 | 985 | repository_url, "application/json", | ||
592 | 986 | json.dumps({"target_link": new_project_url})) | ||
593 | 987 | self.assertEqual(401, response.status) | ||
594 | 988 | with person_logged_in(ANONYMOUS): | ||
595 | 989 | self.assertEqual(project_db, repository_db.target) | ||
596 | 990 | |||
597 | 991 | def test_set_owner(self): | ||
598 | 992 | # The repository owner can reassign the repository to a team they're | ||
599 | 993 | # a member of; this redirects to the new location. | ||
600 | 994 | repository_db = self.factory.makeGitRepository() | ||
601 | 995 | new_owner_db = self.factory.makeTeam(members=[repository_db.owner]) | ||
602 | 996 | webservice = webservice_for_person( | ||
603 | 997 | repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC) | ||
604 | 998 | webservice.default_api_version = "devel" | ||
605 | 999 | with person_logged_in(ANONYMOUS): | ||
606 | 1000 | repository_url = api_url(repository_db) | ||
607 | 1001 | new_owner_url = api_url(new_owner_db) | ||
608 | 1002 | response = webservice.patch( | ||
609 | 1003 | repository_url, "application/json", | ||
610 | 1004 | json.dumps({"owner_link": new_owner_url})) | ||
611 | 1005 | self.assertEqual(301, response.status) | ||
612 | 1006 | with person_logged_in(ANONYMOUS): | ||
613 | 1007 | self.assertEqual( | ||
614 | 1008 | webservice.getAbsoluteUrl(api_url(repository_db)), | ||
615 | 1009 | response.getHeader("Location")) | ||
616 | 1010 | self.assertEqual(new_owner_db, repository_db.owner) | ||
617 | 1011 | |||
618 | 1012 | def test_set_owner_other_person(self): | ||
619 | 1013 | # An unrelated person cannot change the owner. | ||
620 | 1014 | owner_db = self.factory.makePerson() | ||
621 | 1015 | repository_db = self.factory.makeGitRepository(owner=owner_db) | ||
622 | 1016 | new_owner_db = self.factory.makeTeam() | ||
623 | 1017 | webservice = webservice_for_person( | ||
624 | 1018 | new_owner_db.teamowner, permission=OAuthPermission.WRITE_PUBLIC) | ||
625 | 1019 | webservice.default_api_version = "devel" | ||
626 | 1020 | with person_logged_in(ANONYMOUS): | ||
627 | 1021 | repository_url = api_url(repository_db) | ||
628 | 1022 | new_owner_url = api_url(new_owner_db) | ||
629 | 1023 | response = webservice.patch( | ||
630 | 1024 | repository_url, "application/json", | ||
631 | 1025 | json.dumps({"owner_link": new_owner_url})) | ||
632 | 1026 | self.assertEqual(401, response.status) | ||
633 | 1027 | with person_logged_in(ANONYMOUS): | ||
634 | 1028 | self.assertEqual(owner_db, repository_db.owner) | ||
635 | 861 | 1029 | ||
636 | === modified file 'lib/lp/registry/interfaces/sharingservice.py' | |||
637 | --- lib/lp/registry/interfaces/sharingservice.py 2015-02-16 13:08:52 +0000 | |||
638 | +++ lib/lp/registry/interfaces/sharingservice.py 2015-03-06 16:33:15 +0000 | |||
639 | @@ -18,6 +18,7 @@ | |||
640 | 18 | operation_for_version, | 18 | operation_for_version, |
641 | 19 | operation_parameters, | 19 | operation_parameters, |
642 | 20 | operation_returns_collection_of, | 20 | operation_returns_collection_of, |
643 | 21 | rename_parameters_as, | ||
644 | 21 | REQUEST_USER, | 22 | REQUEST_USER, |
645 | 22 | ) | 23 | ) |
646 | 23 | from lazr.restful.fields import Reference | 24 | from lazr.restful.fields import Reference |
647 | @@ -33,6 +34,7 @@ | |||
648 | 33 | from lp.blueprints.interfaces.specification import ISpecification | 34 | from lp.blueprints.interfaces.specification import ISpecification |
649 | 34 | from lp.bugs.interfaces.bug import IBug | 35 | from lp.bugs.interfaces.bug import IBug |
650 | 35 | from lp.code.interfaces.branch import IBranch | 36 | from lp.code.interfaces.branch import IBranch |
651 | 37 | from lp.code.interfaces.gitrepository import IGitRepository | ||
652 | 36 | from lp.registry.enums import ( | 38 | from lp.registry.enums import ( |
653 | 37 | BranchSharingPolicy, | 39 | BranchSharingPolicy, |
654 | 38 | BugSharingPolicy, | 40 | BugSharingPolicy, |
655 | @@ -148,6 +150,13 @@ | |||
656 | 148 | :return: a collection of branches | 150 | :return: a collection of branches |
657 | 149 | """ | 151 | """ |
658 | 150 | 152 | ||
659 | 153 | @export_read_operation() | ||
660 | 154 | @call_with(user=REQUEST_USER) | ||
661 | 155 | @operation_parameters( | ||
662 | 156 | pillar=Reference(IPillar, title=_('Pillar'), required=True), | ||
663 | 157 | person=Reference(IPerson, title=_('Person'), required=True)) | ||
664 | 158 | @operation_returns_collection_of(IGitRepository) | ||
665 | 159 | @operation_for_version('devel') | ||
666 | 151 | def getSharedGitRepositories(pillar, person, user): | 160 | def getSharedGitRepositories(pillar, person, user): |
667 | 152 | """Return the Git repositories shared between the pillar and person. | 161 | """Return the Git repositories shared between the pillar and person. |
668 | 153 | 162 | ||
669 | @@ -312,6 +321,7 @@ | |||
670 | 312 | 321 | ||
671 | 313 | @export_write_operation() | 322 | @export_write_operation() |
672 | 314 | @call_with(user=REQUEST_USER) | 323 | @call_with(user=REQUEST_USER) |
673 | 324 | @rename_parameters_as(gitrepositories='git_repositories') | ||
674 | 315 | @operation_parameters( | 325 | @operation_parameters( |
675 | 316 | pillar=Reference(IPillar, title=_('Pillar'), required=True), | 326 | pillar=Reference(IPillar, title=_('Pillar'), required=True), |
676 | 317 | grantee=Reference(IPerson, title=_('Grantee'), required=True), | 327 | grantee=Reference(IPerson, title=_('Grantee'), required=True), |
677 | @@ -319,6 +329,9 @@ | |||
678 | 319 | Reference(schema=IBug), title=_('Bugs'), required=False), | 329 | Reference(schema=IBug), title=_('Bugs'), required=False), |
679 | 320 | branches=List( | 330 | branches=List( |
680 | 321 | Reference(schema=IBranch), title=_('Branches'), required=False), | 331 | Reference(schema=IBranch), title=_('Branches'), required=False), |
681 | 332 | gitrepositories=List( | ||
682 | 333 | Reference(schema=IGitRepository), | ||
683 | 334 | title=_('Git repositories'), required=False), | ||
684 | 322 | specifications=List( | 335 | specifications=List( |
685 | 323 | Reference(schema=ISpecification), title=_('Specifications'), | 336 | Reference(schema=ISpecification), title=_('Specifications'), |
686 | 324 | required=False)) | 337 | required=False)) |
687 | @@ -338,13 +351,17 @@ | |||
688 | 338 | 351 | ||
689 | 339 | @export_write_operation() | 352 | @export_write_operation() |
690 | 340 | @call_with(user=REQUEST_USER) | 353 | @call_with(user=REQUEST_USER) |
691 | 354 | @rename_parameters_as(gitrepositories='git_repositories') | ||
692 | 341 | @operation_parameters( | 355 | @operation_parameters( |
693 | 342 | grantees=List( | 356 | grantees=List( |
694 | 343 | Reference(IPerson, title=_('Grantee'), required=True)), | 357 | Reference(IPerson, title=_('Grantee'), required=True)), |
695 | 344 | bugs=List( | 358 | bugs=List( |
696 | 345 | Reference(schema=IBug), title=_('Bugs'), required=False), | 359 | Reference(schema=IBug), title=_('Bugs'), required=False), |
697 | 346 | branches=List( | 360 | branches=List( |
699 | 347 | Reference(schema=IBranch), title=_('Branches'), required=False)) | 361 | Reference(schema=IBranch), title=_('Branches'), required=False), |
700 | 362 | gitrepositories=List( | ||
701 | 363 | Reference(schema=IGitRepository), | ||
702 | 364 | title=_('Git repositories'), required=False)) | ||
703 | 348 | @operation_for_version('devel') | 365 | @operation_for_version('devel') |
704 | 349 | def ensureAccessGrants(grantees, user, bugs=None, branches=None, | 366 | def ensureAccessGrants(grantees, user, bugs=None, branches=None, |
705 | 350 | gitrepositories=None, specifications=None): | 367 | gitrepositories=None, specifications=None): |
706 | 351 | 368 | ||
707 | === modified file 'lib/lp/registry/services/tests/test_sharingservice.py' | |||
708 | --- lib/lp/registry/services/tests/test_sharingservice.py 2015-03-04 18:22:06 +0000 | |||
709 | +++ lib/lp/registry/services/tests/test_sharingservice.py 2015-03-06 16:33:15 +0000 | |||
710 | @@ -1949,6 +1949,7 @@ | |||
711 | 1949 | 1949 | ||
712 | 1950 | def setUp(self): | 1950 | def setUp(self): |
713 | 1951 | super(ApiTestMixin, self).setUp() | 1951 | super(ApiTestMixin, self).setUp() |
714 | 1952 | self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"})) | ||
715 | 1952 | self.owner = self.factory.makePerson(name='thundercat') | 1953 | self.owner = self.factory.makePerson(name='thundercat') |
716 | 1953 | self.pillar = self.factory.makeProduct( | 1954 | self.pillar = self.factory.makeProduct( |
717 | 1954 | owner=self.owner, specification_sharing_policy=( | 1955 | owner=self.owner, specification_sharing_policy=( |
718 | @@ -1963,6 +1964,9 @@ | |||
719 | 1963 | self.branch = self.factory.makeBranch( | 1964 | self.branch = self.factory.makeBranch( |
720 | 1964 | owner=self.owner, product=self.pillar, | 1965 | owner=self.owner, product=self.pillar, |
721 | 1965 | information_type=InformationType.PRIVATESECURITY) | 1966 | information_type=InformationType.PRIVATESECURITY) |
722 | 1967 | self.gitrepository = self.factory.makeGitRepository( | ||
723 | 1968 | owner=self.owner, target=self.pillar, | ||
724 | 1969 | information_type=InformationType.PRIVATESECURITY) | ||
725 | 1966 | self.spec = self.factory.makeSpecification( | 1970 | self.spec = self.factory.makeSpecification( |
726 | 1967 | product=self.pillar, owner=self.owner, | 1971 | product=self.pillar, owner=self.owner, |
727 | 1968 | information_type=InformationType.PROPRIETARY) | 1972 | information_type=InformationType.PROPRIETARY) |
728 | @@ -1971,6 +1975,9 @@ | |||
729 | 1971 | self.branch.subscribe( | 1975 | self.branch.subscribe( |
730 | 1972 | self.grantee, BranchSubscriptionNotificationLevel.NOEMAIL, | 1976 | self.grantee, BranchSubscriptionNotificationLevel.NOEMAIL, |
731 | 1973 | None, CodeReviewNotificationLevel.NOEMAIL, self.owner) | 1977 | None, CodeReviewNotificationLevel.NOEMAIL, self.owner) |
732 | 1978 | # XXX cjwatson 2015-02-05: subscribe to Git repository when implemented | ||
733 | 1979 | getUtility(IService, 'sharing').ensureAccessGrants( | ||
734 | 1980 | [self.grantee], self.grantor, gitrepositories=[self.gitrepository]) | ||
735 | 1974 | getUtility(IService, 'sharing').ensureAccessGrants( | 1981 | getUtility(IService, 'sharing').ensureAccessGrants( |
736 | 1975 | [self.grantee], self.grantor, specifications=[self.spec]) | 1982 | [self.grantee], self.grantor, specifications=[self.spec]) |
737 | 1976 | transaction.commit() | 1983 | transaction.commit() |
738 | @@ -2094,6 +2101,16 @@ | |||
739 | 2094 | self.assertEqual(1, len(branches)) | 2101 | self.assertEqual(1, len(branches)) |
740 | 2095 | self.assertEqual(branches[0].unique_name, self.branch.unique_name) | 2102 | self.assertEqual(branches[0].unique_name, self.branch.unique_name) |
741 | 2096 | 2103 | ||
742 | 2104 | def test_getSharedGitRepositories(self): | ||
743 | 2105 | # Test the exported getSharedGitRepositories() method. | ||
744 | 2106 | ws_pillar = ws_object(self.launchpad, self.pillar) | ||
745 | 2107 | ws_grantee = ws_object(self.launchpad, self.grantee) | ||
746 | 2108 | gitrepositories = self.service.getSharedGitRepositories( | ||
747 | 2109 | pillar=ws_pillar, person=ws_grantee) | ||
748 | 2110 | self.assertEqual(1, len(gitrepositories)) | ||
749 | 2111 | self.assertEqual( | ||
750 | 2112 | gitrepositories[0].unique_name, self.gitrepository.unique_name) | ||
751 | 2113 | |||
752 | 2097 | def test_getSharedSpecifications(self): | 2114 | def test_getSharedSpecifications(self): |
753 | 2098 | # Test the exported getSharedSpecifications() method. | 2115 | # Test the exported getSharedSpecifications() method. |
754 | 2099 | ws_pillar = ws_object(self.launchpad, self.pillar) | 2116 | ws_pillar = ws_object(self.launchpad, self.pillar) |
755 | 2100 | 2117 | ||
756 | === modified file 'lib/lp/services/webservice/wadl-to-refhtml.xsl' | |||
757 | --- lib/lp/services/webservice/wadl-to-refhtml.xsl 2013-09-18 06:34:44 +0000 | |||
758 | +++ lib/lp/services/webservice/wadl-to-refhtml.xsl 2015-03-06 16:33:15 +0000 | |||
759 | @@ -168,6 +168,7 @@ | |||
760 | 168 | <xsl:when test=" | 168 | <xsl:when test=" |
761 | 169 | @id = 'bug_link_target' | 169 | @id = 'bug_link_target' |
762 | 170 | or @id = 'bug_target' | 170 | or @id = 'bug_target' |
763 | 171 | or @id = 'git_target' | ||
764 | 171 | or @id = 'has_bugs' | 172 | or @id = 'has_bugs' |
765 | 172 | or @id = 'has_milestones' | 173 | or @id = 'has_milestones' |
766 | 173 | or @id = 'object_with_translation_imports' | 174 | or @id = 'object_with_translation_imports' |
767 | @@ -309,6 +310,28 @@ | |||
768 | 309 | <xsl:text>/+email/</xsl:text> | 310 | <xsl:text>/+email/</xsl:text> |
769 | 310 | <var><email></var> | 311 | <var><email></var> |
770 | 311 | </xsl:when> | 312 | </xsl:when> |
771 | 313 | <xsl:when test="@id = 'git_repository'"> | ||
772 | 314 | <xsl:text>/~</xsl:text> | ||
773 | 315 | <var><person.name></var> | ||
774 | 316 | <xsl:text>/</xsl:text> | ||
775 | 317 | <var><project.name></var> | ||
776 | 318 | <xsl:text>/+git/</xsl:text> | ||
777 | 319 | <var><repository.name></var> | ||
778 | 320 | or | ||
779 | 321 | <xsl:text>/~</xsl:text> | ||
780 | 322 | <var><person.name></var> | ||
781 | 323 | <xsl:text>/</xsl:text> | ||
782 | 324 | <var><distribution.name></var> | ||
783 | 325 | <xsl:text>/+source/</xsl:text> | ||
784 | 326 | <var><source_package.name></var> | ||
785 | 327 | <xsl:text>/+git/</xsl:text> | ||
786 | 328 | <var><repository.name></var> | ||
787 | 329 | or | ||
788 | 330 | <xsl:text>/~</xsl:text> | ||
789 | 331 | <var><person.name></var> | ||
790 | 332 | <xsl:text>/+git/</xsl:text> | ||
791 | 333 | <var><repository.name></var> | ||
792 | 334 | </xsl:when> | ||
793 | 312 | <xsl:when test="@id = 'gpg_key'"> | 335 | <xsl:when test="@id = 'gpg_key'"> |
794 | 313 | <xsl:text>/</xsl:text> | 336 | <xsl:text>/</xsl:text> |
795 | 314 | <var><person.name></var> | 337 | <var><person.name></var> |