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

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
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 IGitRepositorySet.new, because currently we want you to do that by pushing a repository instead. We'll need to work on the workflows there, but we can easily export that method later.

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
=== modified file 'lib/lp/app/browser/launchpad.py'
--- lib/lp/app/browser/launchpad.py 2014-11-27 11:01:16 +0000
+++ lib/lp/app/browser/launchpad.py 2015-03-06 16:33:15 +0000
@@ -102,6 +102,7 @@
102from lp.code.interfaces.branchlookup import IBranchLookup102from lp.code.interfaces.branchlookup import IBranchLookup
103from lp.code.interfaces.codehosting import IBazaarApplication103from lp.code.interfaces.codehosting import IBazaarApplication
104from lp.code.interfaces.codeimport import ICodeImportSet104from lp.code.interfaces.codeimport import ICodeImportSet
105from lp.code.interfaces.gitrepository import IGitRepositorySet
105from lp.hardwaredb.interfaces.hwdb import IHWDBApplication106from lp.hardwaredb.interfaces.hwdb import IHWDBApplication
106from lp.layers import WebServiceLayer107from lp.layers import WebServiceLayer
107from lp.registry.interfaces.announcement import IAnnouncementSet108from lp.registry.interfaces.announcement import IAnnouncementSet
@@ -783,6 +784,7 @@
783 'codeofconduct': ICodeOfConductSet,784 'codeofconduct': ICodeOfConductSet,
784 '+countries': ICountrySet,785 '+countries': ICountrySet,
785 'distros': IDistributionSet,786 'distros': IDistributionSet,
787 '+git': IGitRepositorySet,
786 '+hwdb': IHWDBApplication,788 '+hwdb': IHWDBApplication,
787 'karmaaction': IKarmaActionSet,789 'karmaaction': IKarmaActionSet,
788 '+imports': ITranslationImportQueue,790 '+imports': ITranslationImportQueue,
789791
=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml 2015-03-04 16:56:48 +0000
+++ lib/lp/code/browser/configure.zcml 2015-03-06 16:33:15 +0000
@@ -13,6 +13,11 @@
13 path_expression="string:branches"13 path_expression="string:branches"
14 parent_utility="lp.services.webapp.interfaces.ILaunchpadRoot"14 parent_utility="lp.services.webapp.interfaces.ILaunchpadRoot"
15 />15 />
16 <browser:url
17 for="lp.code.interfaces.gitrepository.IGitRepositorySet"
18 path_expression="string:+git"
19 parent_utility="lp.services.webapp.interfaces.ILaunchpadRoot"
20 />
16 <browser:feeds21 <browser:feeds
17 module="lp.code.feed.branch"22 module="lp.code.feed.branch"
18 classes="BranchFeed PersonBranchFeed ProductBranchFeed ProjectBranchFeed23 classes="BranchFeed PersonBranchFeed ProductBranchFeed ProjectBranchFeed
1924
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml 2015-02-26 17:20:31 +0000
+++ lib/lp/code/configure.zcml 2015-03-06 16:33:15 +0000
@@ -818,7 +818,8 @@
818 <require818 <require
819 permission="launchpad.Moderate"819 permission="launchpad.Moderate"
820 interface="lp.code.interfaces.gitrepository.IGitRepositoryModerate"820 interface="lp.code.interfaces.gitrepository.IGitRepositoryModerate"
821 set_schema="lp.code.interfaces.gitrepository.IGitRepositoryModerateAttributes" />821 set_schema="lp.code.interfaces.gitrepository.IGitRepositoryModerateAttributes"
822 set_attributes="date_last_modified" />
822 <require823 <require
823 permission="launchpad.Edit"824 permission="launchpad.Edit"
824 interface="lp.code.interfaces.gitrepository.IGitRepositoryEdit" />825 interface="lp.code.interfaces.gitrepository.IGitRepositoryEdit" />
825826
=== modified file 'lib/lp/code/interfaces/gitrepository.py'
--- lib/lp/code/interfaces/gitrepository.py 2015-03-05 14:13:16 +0000
+++ lib/lp/code/interfaces/gitrepository.py 2015-03-06 16:33:15 +0000
@@ -17,7 +17,24 @@
1717
18import re18import re
1919
20from lazr.restful.declarations import (
21 call_with,
22 collection_default_content,
23 export_as_webservice_collection,
24 export_as_webservice_entry,
25 export_destructor_operation,
26 export_read_operation,
27 export_write_operation,
28 exported,
29 mutator_for,
30 operation_for_version,
31 operation_parameters,
32 operation_returns_collection_of,
33 operation_returns_entry,
34 REQUEST_USER,
35 )
20from lazr.restful.fields import Reference36from lazr.restful.fields import Reference
37from lazr.restful.interface import copy_field
21from zope.component import getUtility38from zope.component import getUtility
22from zope.interface import (39from zope.interface import (
23 Attribute,40 Attribute,
@@ -40,6 +57,7 @@
40from lp.registry.interfaces.distributionsourcepackage import (57from lp.registry.interfaces.distributionsourcepackage import (
41 IDistributionSourcePackage,58 IDistributionSourcePackage,
42 )59 )
60from lp.registry.interfaces.person import IPerson
43from lp.registry.interfaces.persondistributionsourcepackage import (61from lp.registry.interfaces.persondistributionsourcepackage import (
44 IPersonDistributionSourcePackageFactory,62 IPersonDistributionSourcePackageFactory,
45 )63 )
@@ -97,68 +115,76 @@
97115
98 id = Int(title=_("ID"), readonly=True, required=True)116 id = Int(title=_("ID"), readonly=True, required=True)
99117
100 date_created = Datetime(118 date_created = exported(Datetime(
101 title=_("Date created"), required=True, readonly=True)119 title=_("Date created"), required=True, readonly=True))
102120
103 date_last_modified = Datetime(121 registrant = exported(PublicPersonChoice(
104 title=_("Date last modified"), required=True, readonly=True)
105
106 registrant = PublicPersonChoice(
107 title=_("Registrant"), required=True, readonly=True,122 title=_("Registrant"), required=True, readonly=True,
108 vocabulary="ValidPersonOrTeam",123 vocabulary="ValidPersonOrTeam",
109 description=_("The person who registered this Git repository."))124 description=_("The person who registered this Git repository.")))
110125
111 owner = PersonChoice(126 owner = exported(PersonChoice(
112 title=_("Owner"), required=True, readonly=False,127 title=_("Owner"), required=True, readonly=True,
113 vocabulary="AllUserTeamsParticipationPlusSelf",128 vocabulary="AllUserTeamsParticipationPlusSelf",
114 description=_(129 description=_(
115 "The owner of this Git repository. This controls who can modify "130 "The owner of this Git repository. This controls who can modify "
116 "the repository."))131 "the repository.")))
117132
118 target = Reference(133 target = exported(
119 title=_("Target"), required=True, readonly=True,134 Reference(
120 schema=IHasGitRepositories,135 title=_("Target"), required=True, readonly=True,
121 description=_("The target of the repository."))136 schema=IHasGitRepositories,
137 description=_("The target of the repository.")),
138 as_of="devel")
122139
123 namespace = Attribute(140 namespace = Attribute(
124 "The namespace of this repository, as an `IGitNamespace`.")141 "The namespace of this repository, as an `IGitNamespace`.")
125142
126 information_type = Choice(143 # XXX cjwatson 2015-01-29: Add some advice about default repository
144 # naming.
145 name = exported(TextLine(
146 title=_("Name"), required=True, readonly=True,
147 constraint=git_repository_name_validator,
148 description=_(
149 "The repository name. Keep very short, unique, and descriptive, "
150 "because it will be used in URLs.")))
151
152 information_type = exported(Choice(
127 title=_("Information type"), vocabulary=InformationType,153 title=_("Information type"), vocabulary=InformationType,
128 required=True, readonly=True, default=InformationType.PUBLIC,154 required=True, readonly=True, default=InformationType.PUBLIC,
129 description=_(155 description=_(
130 "The type of information contained in this repository."))156 "The type of information contained in this repository.")))
131157
132 owner_default = Bool(158 owner_default = exported(Bool(
133 title=_("Owner default"), required=True, readonly=True,159 title=_("Owner default"), required=True, readonly=True,
134 description=_(160 description=_(
135 "Whether this repository is the default for its owner and "161 "Whether this repository is the default for its owner and "
136 "target."))162 "target.")))
137163
138 target_default = Bool(164 target_default = exported(Bool(
139 title=_("Target default"), required=True, readonly=True,165 title=_("Target default"), required=True, readonly=True,
140 description=_(166 description=_(
141 "Whether this repository is the default for its target."))167 "Whether this repository is the default for its target.")))
142168
143 unique_name = Text(169 unique_name = exported(Text(
144 title=_("Unique name"), readonly=True,170 title=_("Unique name"), readonly=True,
145 description=_(171 description=_(
146 "Unique name of the repository, including the owner and project "172 "Unique name of the repository, including the owner and project "
147 "names."))173 "names.")))
148174
149 display_name = Text(175 display_name = exported(Text(
150 title=_("Display name"), readonly=True,176 title=_("Display name"), readonly=True,
151 description=_("Display name of the repository."))177 description=_("Display name of the repository.")))
152178
153 shortened_path = Attribute(179 shortened_path = Attribute(
154 "The shortest reasonable version of the path to this repository.")180 "The shortest reasonable version of the path to this repository.")
155181
156 git_identity = Text(182 git_identity = exported(Text(
157 title=_("Git identity"), readonly=True,183 title=_("Git identity"), readonly=True,
158 description=_(184 description=_(
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 "
160 "'lp:' plus a shortcut version of the path via that target. "186 "'lp:' plus a shortcut version of the path via that target. "
161 "Otherwise it is simply 'lp:' plus the unique name."))187 "Otherwise it is simply 'lp:' plus the unique name.")))
162188
163 def setOwnerDefault(value):189 def setOwnerDefault(value):
164 """Set whether this repository is the default for its owner-target.190 """Set whether this repository is the default for its owner-target.
@@ -242,19 +268,20 @@
242 """IGitRepository attributes that can be edited by more than one community.268 """IGitRepository attributes that can be edited by more than one community.
243 """269 """
244270
245 # XXX cjwatson 2015-01-29: Add some advice about default repository271 date_last_modified = exported(Datetime(
246 # naming.272 title=_("Date last modified"), required=True, readonly=True))
247 name = TextLine(
248 title=_("Name"), required=True,
249 constraint=git_repository_name_validator,
250 description=_(
251 "The repository name. Keep very short, unique, and descriptive, "
252 "because it will be used in URLs."))
253273
254274
255class IGitRepositoryModerate(Interface):275class IGitRepositoryModerate(Interface):
256 """IGitRepository methods that can be called by more than one community."""276 """IGitRepository methods that can be called by more than one community."""
257277
278 @mutator_for(IGitRepositoryView["information_type"])
279 @operation_parameters(
280 information_type=copy_field(IGitRepositoryView["information_type"]),
281 )
282 @call_with(user=REQUEST_USER)
283 @export_write_operation()
284 @operation_for_version("devel")
258 def transitionToInformationType(information_type, user,285 def transitionToInformationType(information_type, user,
259 verify_policy=True):286 verify_policy=True):
260 """Set the information type for this repository.287 """Set the information type for this repository.
@@ -269,12 +296,31 @@
269class IGitRepositoryEdit(Interface):296class IGitRepositoryEdit(Interface):
270 """IGitRepository methods that require launchpad.Edit permission."""297 """IGitRepository methods that require launchpad.Edit permission."""
271298
299 @mutator_for(IGitRepositoryView["owner"])
300 @call_with(user=REQUEST_USER)
301 @operation_parameters(
302 new_owner=Reference(
303 title=_("The new owner of the repository."), schema=IPerson))
304 @export_write_operation()
305 @operation_for_version("devel")
272 def setOwner(new_owner, user):306 def setOwner(new_owner, user):
273 """Set the owner of the repository to be `new_owner`."""307 """Set the owner of the repository to be `new_owner`."""
274308
309 @mutator_for(IGitRepositoryView["target"])
310 @call_with(user=REQUEST_USER)
311 @operation_parameters(
312 target=Reference(
313 title=_(
314 "The project, distribution source package, or person the "
315 "repository belongs to."),
316 schema=IHasGitRepositories, required=True))
317 @export_write_operation()
318 @operation_for_version("devel")
275 def setTarget(target, user):319 def setTarget(target, user):
276 """Set the target of the repository."""320 """Set the target of the repository."""
277321
322 @export_destructor_operation()
323 @operation_for_version("devel")
278 def destroySelf():324 def destroySelf():
279 """Delete the specified repository."""325 """Delete the specified repository."""
280326
@@ -283,14 +329,22 @@
283 IGitRepositoryModerate, IGitRepositoryEdit):329 IGitRepositoryModerate, IGitRepositoryEdit):
284 """A Git repository."""330 """A Git repository."""
285331
286 private = Bool(332 # Mark repositories as exported entries for the Launchpad API.
333 # XXX cjwatson 2015-01-19 bug=760849: "beta" is a lie to get WADL
334 # generation working. Individual attributes must set their version to
335 # "devel".
336 export_as_webservice_entry(plural_name="git_repositories", as_of="beta")
337
338 private = exported(Bool(
287 title=_("Private"), required=False, readonly=True,339 title=_("Private"), required=False, readonly=True,
288 description=_("This repository is visible only to its subscribers."))340 description=_("This repository is visible only to its subscribers.")))
289341
290342
291class IGitRepositorySet(Interface):343class IGitRepositorySet(Interface):
292 """Interface representing the set of Git repositories."""344 """Interface representing the set of Git repositories."""
293345
346 export_as_webservice_collection(IGitRepository)
347
294 def new(registrant, owner, target, name, information_type=None,348 def new(registrant, owner, target, name, information_type=None,
295 date_created=None):349 date_created=None):
296 """Create a Git repository and return it.350 """Create a Git repository and return it.
@@ -306,6 +360,12 @@
306 """360 """
307361
308 # Marker for references to Git URL layouts: ##GITNAMESPACE##362 # Marker for references to Git URL layouts: ##GITNAMESPACE##
363 @call_with(user=REQUEST_USER)
364 @operation_parameters(
365 path=TextLine(title=_("Repository path"), required=True))
366 @operation_returns_entry(IGitRepository)
367 @export_read_operation()
368 @operation_for_version("devel")
309 def getByPath(user, path):369 def getByPath(user, path):
310 """Find a repository by its path.370 """Find a repository by its path.
311371
@@ -324,6 +384,13 @@
324 Return None if no match was found.384 Return None if no match was found.
325 """385 """
326386
387 @call_with(user=REQUEST_USER)
388 @operation_parameters(
389 target=Reference(
390 title=_("Target"), required=True, schema=IHasGitRepositories))
391 @operation_returns_collection_of(IGitRepository)
392 @export_read_operation()
393 @operation_for_version("devel")
327 def getRepositories(user, target):394 def getRepositories(user, target):
328 """Get all repositories for a target.395 """Get all repositories for a target.
329396
@@ -334,6 +401,12 @@
334 :return: A collection of `IGitRepository` objects.401 :return: A collection of `IGitRepository` objects.
335 """402 """
336403
404 @operation_parameters(
405 target=Reference(
406 title=_("Target"), required=True, schema=IHasGitRepositories))
407 @operation_returns_entry(IGitRepository)
408 @export_read_operation()
409 @operation_for_version("devel")
337 def getDefaultRepository(target):410 def getDefaultRepository(target):
338 """Get the default repository for a target.411 """Get the default repository for a target.
339412
@@ -343,6 +416,13 @@
343 :return: An `IGitRepository`, or None.416 :return: An `IGitRepository`, or None.
344 """417 """
345418
419 @operation_parameters(
420 owner=Reference(title=_("Owner"), required=True, schema=IPerson),
421 target=Reference(
422 title=_("Target"), required=True, schema=IHasGitRepositories))
423 @operation_returns_entry(IGitRepository)
424 @export_read_operation()
425 @operation_for_version("devel")
346 def getDefaultRepositoryForOwner(owner, target):426 def getDefaultRepositoryForOwner(owner, target):
347 """Get a person's default repository for a target.427 """Get a person's default repository for a target.
348428
@@ -353,6 +433,13 @@
353 :return: An `IGitRepository`, or None.433 :return: An `IGitRepository`, or None.
354 """434 """
355435
436 @operation_parameters(
437 target=Reference(
438 title=_("Target"), required=True, schema=IHasGitRepositories),
439 repository=Reference(
440 title=_("Git repository"), required=False, schema=IGitRepository))
441 @export_write_operation()
442 @operation_for_version("devel")
356 def setDefaultRepository(target, repository):443 def setDefaultRepository(target, repository):
357 """Set the default repository for a target.444 """Set the default repository for a target.
358445
@@ -363,6 +450,14 @@
363 :raises GitTargetError: if `target` is an `IPerson`.450 :raises GitTargetError: if `target` is an `IPerson`.
364 """451 """
365452
453 @operation_parameters(
454 owner=Reference(title=_("Owner"), required=True, schema=IPerson),
455 target=Reference(
456 title=_("Target"), required=True, schema=IHasGitRepositories),
457 repository=Reference(
458 title=_("Git repository"), required=False, schema=IGitRepository))
459 @export_write_operation()
460 @operation_for_version("devel")
366 def setDefaultRepositoryForOwner(owner, target, repository):461 def setDefaultRepositoryForOwner(owner, target, repository):
367 """Set a person's default repository for a target.462 """Set a person's default repository for a target.
368463
@@ -374,6 +469,7 @@
374 :raises GitTargetError: if `target` is an `IPerson`.469 :raises GitTargetError: if `target` is an `IPerson`.
375 """470 """
376471
472 @collection_default_content()
377 def empty_list():473 def empty_list():
378 """Return an empty collection of repositories.474 """Return an empty collection of repositories.
379475
380476
=== modified file 'lib/lp/code/interfaces/hasgitrepositories.py'
--- lib/lp/code/interfaces/hasgitrepositories.py 2015-03-04 19:05:47 +0000
+++ lib/lp/code/interfaces/hasgitrepositories.py 2015-03-06 16:33:15 +0000
@@ -9,6 +9,7 @@
9 'IHasGitRepositories',9 'IHasGitRepositories',
10 ]10 ]
1111
12from lazr.restful.declarations import export_as_webservice_entry
12from zope.interface import Interface13from zope.interface import Interface
1314
1415
@@ -18,3 +19,6 @@
18 A project contains Git repositories, a source package on a distribution19 A project contains Git repositories, a source package on a distribution
19 contains branches, and a person contains "personal" branches.20 contains branches, and a person contains "personal" branches.
20 """21 """
22
23 export_as_webservice_entry(
24 singular_name="git_target", plural_name="git_targets", as_of="devel")
2125
=== modified file 'lib/lp/code/interfaces/webservice.py'
--- lib/lp/code/interfaces/webservice.py 2015-01-30 18:24:07 +0000
+++ lib/lp/code/interfaces/webservice.py 2015-03-06 16:33:15 +0000
@@ -25,6 +25,9 @@
25 'ICodeReviewComment',25 'ICodeReviewComment',
26 'ICodeReviewVoteReference',26 'ICodeReviewVoteReference',
27 'IDiff',27 'IDiff',
28 'IGitRepository',
29 'IGitRepositorySet',
30 'IHasGitRepositories',
28 'IPreviewDiff',31 'IPreviewDiff',
29 'ISourcePackageRecipe',32 'ISourcePackageRecipe',
30 'ISourcePackageRecipeBuild',33 'ISourcePackageRecipeBuild',
@@ -57,6 +60,11 @@
57 IDiff,60 IDiff,
58 IPreviewDiff,61 IPreviewDiff,
59 )62 )
63from lp.code.interfaces.gitrepository import (
64 IGitRepository,
65 IGitRepositorySet,
66 )
67from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
60from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe68from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
61from lp.code.interfaces.sourcepackagerecipebuild import (69from lp.code.interfaces.sourcepackagerecipebuild import (
62 ISourcePackageRecipeBuild,70 ISourcePackageRecipeBuild,
6371
=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
--- lib/lp/code/model/tests/test_gitrepository.py 2015-03-05 14:13:16 +0000
+++ lib/lp/code/model/tests/test_gitrepository.py 2015-03-06 16:33:15 +0000
@@ -7,6 +7,7 @@
77
8from datetime import datetime8from datetime import datetime
9from functools import partial9from functools import partial
10import json
1011
11from lazr.lifecycle.event import ObjectModifiedEvent12from lazr.lifecycle.event import ObjectModifiedEvent
12import pytz13import pytz
@@ -54,8 +55,11 @@
54from lp.services.database.constants import UTC_NOW55from lp.services.database.constants import UTC_NOW
55from lp.services.features.testing import FeatureFixture56from lp.services.features.testing import FeatureFixture
56from lp.services.webapp.authorization import check_permission57from lp.services.webapp.authorization import check_permission
58from lp.services.webapp.interfaces import OAuthPermission
57from lp.testing import (59from lp.testing import (
58 admin_logged_in,60 admin_logged_in,
61 ANONYMOUS,
62 api_url,
59 celebrity_logged_in,63 celebrity_logged_in,
60 person_logged_in,64 person_logged_in,
61 TestCaseWithFactory,65 TestCaseWithFactory,
@@ -65,6 +69,7 @@
65 DatabaseFunctionalLayer,69 DatabaseFunctionalLayer,
66 ZopelessDatabaseLayer,70 ZopelessDatabaseLayer,
67 )71 )
72from lp.testing.pages import webservice_for_person
6873
6974
70class TestGitRepositoryFeatureFlag(TestCaseWithFactory):75class TestGitRepositoryFeatureFlag(TestCaseWithFactory):
@@ -476,14 +481,6 @@
476 self.assertEqual(481 self.assertEqual(
477 InformationType.PRIVATESECURITY, repository.information_type)482 InformationType.PRIVATESECURITY, repository.information_type)
478483
479 def test_attribute_smoketest(self):
480 # Users with launchpad.Moderate can set attributes.
481 project = self.factory.makeProduct()
482 repository = self.factory.makeGitRepository(target=project)
483 with person_logged_in(project.owner):
484 repository.name = u"not-secret"
485 self.assertEqual(u"not-secret", repository.name)
486
487484
488class TestGitRepositorySetOwner(TestCaseWithFactory):485class TestGitRepositorySetOwner(TestCaseWithFactory):
489 """Test `IGitRepository.setOwner`."""486 """Test `IGitRepository.setOwner`."""
@@ -858,3 +855,174 @@
858 TestGitRepositorySetDefaultsOwnerMixin,855 TestGitRepositorySetDefaultsOwnerMixin,
859 TestGitRepositorySetDefaultsPackage):856 TestGitRepositorySetDefaultsPackage):
860 pass857 pass
858
859
860class TestGitRepositoryWebservice(TestCaseWithFactory):
861 """Tests for the webservice."""
862
863 layer = DatabaseFunctionalLayer
864
865 def setUp(self):
866 super(TestGitRepositoryWebservice, self).setUp()
867 self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
868
869 def test_getRepositories_project(self):
870 project_db = self.factory.makeProduct()
871 repository_db = self.factory.makeGitRepository(target=project_db)
872 webservice = webservice_for_person(
873 repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
874 webservice.default_api_version = "devel"
875 with person_logged_in(ANONYMOUS):
876 repository_url = api_url(repository_db)
877 owner_url = api_url(repository_db.owner)
878 project_url = api_url(project_db)
879 response = webservice.named_get(
880 "/+git", "getRepositories", user=owner_url, target=project_url)
881 self.assertEqual(200, response.status)
882 self.assertEqual(
883 [webservice.getAbsoluteUrl(repository_url)],
884 [entry["self_link"] for entry in response.jsonBody()["entries"]])
885
886 def test_getRepositories_package(self):
887 dsp_db = self.factory.makeDistributionSourcePackage()
888 repository_db = self.factory.makeGitRepository(target=dsp_db)
889 webservice = webservice_for_person(
890 repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
891 webservice.default_api_version = "devel"
892 with person_logged_in(ANONYMOUS):
893 repository_url = api_url(repository_db)
894 owner_url = api_url(repository_db.owner)
895 dsp_url = api_url(dsp_db)
896 response = webservice.named_get(
897 "/+git", "getRepositories", user=owner_url, target=dsp_url)
898 self.assertEqual(200, response.status)
899 self.assertEqual(
900 [webservice.getAbsoluteUrl(repository_url)],
901 [entry["self_link"] for entry in response.jsonBody()["entries"]])
902
903 def test_getRepositories_personal(self):
904 owner_db = self.factory.makePerson()
905 repository_db = self.factory.makeGitRepository(
906 owner=owner_db, target=owner_db)
907 webservice = webservice_for_person(
908 owner_db, permission=OAuthPermission.WRITE_PUBLIC)
909 webservice.default_api_version = "devel"
910 with person_logged_in(ANONYMOUS):
911 repository_url = api_url(repository_db)
912 owner_url = api_url(owner_db)
913 response = webservice.named_get(
914 "/+git", "getRepositories", user=owner_url, target=owner_url)
915 self.assertEqual(200, response.status)
916 self.assertEqual(
917 [webservice.getAbsoluteUrl(repository_url)],
918 [entry["self_link"] for entry in response.jsonBody()["entries"]])
919
920 def test_set_information_type(self):
921 # The repository owner can change the information type.
922 repository_db = self.factory.makeGitRepository()
923 webservice = webservice_for_person(
924 repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
925 webservice.default_api_version = "devel"
926 with person_logged_in(ANONYMOUS):
927 repository_url = api_url(repository_db)
928 response = webservice.patch(
929 repository_url, "application/json",
930 json.dumps({"information_type": "Public Security"}))
931 self.assertEqual(209, response.status)
932 with person_logged_in(ANONYMOUS):
933 self.assertEqual(
934 InformationType.PUBLICSECURITY, repository_db.information_type)
935
936 def test_set_information_type_other_person(self):
937 # An unrelated user cannot change the information type.
938 repository_db = self.factory.makeGitRepository()
939 webservice = webservice_for_person(
940 self.factory.makePerson(), permission=OAuthPermission.WRITE_PUBLIC)
941 webservice.default_api_version = "devel"
942 with person_logged_in(ANONYMOUS):
943 repository_url = api_url(repository_db)
944 response = webservice.patch(
945 repository_url, "application/json",
946 json.dumps({"information_type": "Public Security"}))
947 self.assertEqual(401, response.status)
948 with person_logged_in(ANONYMOUS):
949 self.assertEqual(
950 InformationType.PUBLIC, repository_db.information_type)
951
952 def test_set_target(self):
953 # The repository owner can move the repository to another target;
954 # this redirects to the new location.
955 repository_db = self.factory.makeGitRepository()
956 new_project_db = self.factory.makeProduct()
957 webservice = webservice_for_person(
958 repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
959 webservice.default_api_version = "devel"
960 with person_logged_in(ANONYMOUS):
961 repository_url = api_url(repository_db)
962 new_project_url = api_url(new_project_db)
963 response = webservice.patch(
964 repository_url, "application/json",
965 json.dumps({"target_link": new_project_url}))
966 self.assertEqual(301, response.status)
967 with person_logged_in(ANONYMOUS):
968 self.assertEqual(
969 webservice.getAbsoluteUrl(api_url(repository_db)),
970 response.getHeader("Location"))
971 self.assertEqual(new_project_db, repository_db.target)
972
973 def test_set_target_other_person(self):
974 # An unrelated person cannot change the target.
975 project_db = self.factory.makeProduct()
976 repository_db = self.factory.makeGitRepository(target=project_db)
977 new_project_db = self.factory.makeProduct()
978 webservice = webservice_for_person(
979 self.factory.makePerson(), permission=OAuthPermission.WRITE_PUBLIC)
980 webservice.default_api_version = "devel"
981 with person_logged_in(ANONYMOUS):
982 repository_url = api_url(repository_db)
983 new_project_url = api_url(new_project_db)
984 response = webservice.patch(
985 repository_url, "application/json",
986 json.dumps({"target_link": new_project_url}))
987 self.assertEqual(401, response.status)
988 with person_logged_in(ANONYMOUS):
989 self.assertEqual(project_db, repository_db.target)
990
991 def test_set_owner(self):
992 # The repository owner can reassign the repository to a team they're
993 # a member of; this redirects to the new location.
994 repository_db = self.factory.makeGitRepository()
995 new_owner_db = self.factory.makeTeam(members=[repository_db.owner])
996 webservice = webservice_for_person(
997 repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
998 webservice.default_api_version = "devel"
999 with person_logged_in(ANONYMOUS):
1000 repository_url = api_url(repository_db)
1001 new_owner_url = api_url(new_owner_db)
1002 response = webservice.patch(
1003 repository_url, "application/json",
1004 json.dumps({"owner_link": new_owner_url}))
1005 self.assertEqual(301, response.status)
1006 with person_logged_in(ANONYMOUS):
1007 self.assertEqual(
1008 webservice.getAbsoluteUrl(api_url(repository_db)),
1009 response.getHeader("Location"))
1010 self.assertEqual(new_owner_db, repository_db.owner)
1011
1012 def test_set_owner_other_person(self):
1013 # An unrelated person cannot change the owner.
1014 owner_db = self.factory.makePerson()
1015 repository_db = self.factory.makeGitRepository(owner=owner_db)
1016 new_owner_db = self.factory.makeTeam()
1017 webservice = webservice_for_person(
1018 new_owner_db.teamowner, permission=OAuthPermission.WRITE_PUBLIC)
1019 webservice.default_api_version = "devel"
1020 with person_logged_in(ANONYMOUS):
1021 repository_url = api_url(repository_db)
1022 new_owner_url = api_url(new_owner_db)
1023 response = webservice.patch(
1024 repository_url, "application/json",
1025 json.dumps({"owner_link": new_owner_url}))
1026 self.assertEqual(401, response.status)
1027 with person_logged_in(ANONYMOUS):
1028 self.assertEqual(owner_db, repository_db.owner)
8611029
=== modified file 'lib/lp/registry/interfaces/sharingservice.py'
--- lib/lp/registry/interfaces/sharingservice.py 2015-02-16 13:08:52 +0000
+++ lib/lp/registry/interfaces/sharingservice.py 2015-03-06 16:33:15 +0000
@@ -18,6 +18,7 @@
18 operation_for_version,18 operation_for_version,
19 operation_parameters,19 operation_parameters,
20 operation_returns_collection_of,20 operation_returns_collection_of,
21 rename_parameters_as,
21 REQUEST_USER,22 REQUEST_USER,
22 )23 )
23from lazr.restful.fields import Reference24from lazr.restful.fields import Reference
@@ -33,6 +34,7 @@
33from lp.blueprints.interfaces.specification import ISpecification34from lp.blueprints.interfaces.specification import ISpecification
34from lp.bugs.interfaces.bug import IBug35from lp.bugs.interfaces.bug import IBug
35from lp.code.interfaces.branch import IBranch36from lp.code.interfaces.branch import IBranch
37from lp.code.interfaces.gitrepository import IGitRepository
36from lp.registry.enums import (38from lp.registry.enums import (
37 BranchSharingPolicy,39 BranchSharingPolicy,
38 BugSharingPolicy,40 BugSharingPolicy,
@@ -148,6 +150,13 @@
148 :return: a collection of branches150 :return: a collection of branches
149 """151 """
150152
153 @export_read_operation()
154 @call_with(user=REQUEST_USER)
155 @operation_parameters(
156 pillar=Reference(IPillar, title=_('Pillar'), required=True),
157 person=Reference(IPerson, title=_('Person'), required=True))
158 @operation_returns_collection_of(IGitRepository)
159 @operation_for_version('devel')
151 def getSharedGitRepositories(pillar, person, user):160 def getSharedGitRepositories(pillar, person, user):
152 """Return the Git repositories shared between the pillar and person.161 """Return the Git repositories shared between the pillar and person.
153162
@@ -312,6 +321,7 @@
312321
313 @export_write_operation()322 @export_write_operation()
314 @call_with(user=REQUEST_USER)323 @call_with(user=REQUEST_USER)
324 @rename_parameters_as(gitrepositories='git_repositories')
315 @operation_parameters(325 @operation_parameters(
316 pillar=Reference(IPillar, title=_('Pillar'), required=True),326 pillar=Reference(IPillar, title=_('Pillar'), required=True),
317 grantee=Reference(IPerson, title=_('Grantee'), required=True),327 grantee=Reference(IPerson, title=_('Grantee'), required=True),
@@ -319,6 +329,9 @@
319 Reference(schema=IBug), title=_('Bugs'), required=False),329 Reference(schema=IBug), title=_('Bugs'), required=False),
320 branches=List(330 branches=List(
321 Reference(schema=IBranch), title=_('Branches'), required=False),331 Reference(schema=IBranch), title=_('Branches'), required=False),
332 gitrepositories=List(
333 Reference(schema=IGitRepository),
334 title=_('Git repositories'), required=False),
322 specifications=List(335 specifications=List(
323 Reference(schema=ISpecification), title=_('Specifications'),336 Reference(schema=ISpecification), title=_('Specifications'),
324 required=False))337 required=False))
@@ -338,13 +351,17 @@
338351
339 @export_write_operation()352 @export_write_operation()
340 @call_with(user=REQUEST_USER)353 @call_with(user=REQUEST_USER)
354 @rename_parameters_as(gitrepositories='git_repositories')
341 @operation_parameters(355 @operation_parameters(
342 grantees=List(356 grantees=List(
343 Reference(IPerson, title=_('Grantee'), required=True)),357 Reference(IPerson, title=_('Grantee'), required=True)),
344 bugs=List(358 bugs=List(
345 Reference(schema=IBug), title=_('Bugs'), required=False),359 Reference(schema=IBug), title=_('Bugs'), required=False),
346 branches=List(360 branches=List(
347 Reference(schema=IBranch), title=_('Branches'), required=False))361 Reference(schema=IBranch), title=_('Branches'), required=False),
362 gitrepositories=List(
363 Reference(schema=IGitRepository),
364 title=_('Git repositories'), required=False))
348 @operation_for_version('devel')365 @operation_for_version('devel')
349 def ensureAccessGrants(grantees, user, bugs=None, branches=None,366 def ensureAccessGrants(grantees, user, bugs=None, branches=None,
350 gitrepositories=None, specifications=None):367 gitrepositories=None, specifications=None):
351368
=== modified file 'lib/lp/registry/services/tests/test_sharingservice.py'
--- lib/lp/registry/services/tests/test_sharingservice.py 2015-03-04 18:22:06 +0000
+++ lib/lp/registry/services/tests/test_sharingservice.py 2015-03-06 16:33:15 +0000
@@ -1949,6 +1949,7 @@
19491949
1950 def setUp(self):1950 def setUp(self):
1951 super(ApiTestMixin, self).setUp()1951 super(ApiTestMixin, self).setUp()
1952 self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
1952 self.owner = self.factory.makePerson(name='thundercat')1953 self.owner = self.factory.makePerson(name='thundercat')
1953 self.pillar = self.factory.makeProduct(1954 self.pillar = self.factory.makeProduct(
1954 owner=self.owner, specification_sharing_policy=(1955 owner=self.owner, specification_sharing_policy=(
@@ -1963,6 +1964,9 @@
1963 self.branch = self.factory.makeBranch(1964 self.branch = self.factory.makeBranch(
1964 owner=self.owner, product=self.pillar,1965 owner=self.owner, product=self.pillar,
1965 information_type=InformationType.PRIVATESECURITY)1966 information_type=InformationType.PRIVATESECURITY)
1967 self.gitrepository = self.factory.makeGitRepository(
1968 owner=self.owner, target=self.pillar,
1969 information_type=InformationType.PRIVATESECURITY)
1966 self.spec = self.factory.makeSpecification(1970 self.spec = self.factory.makeSpecification(
1967 product=self.pillar, owner=self.owner,1971 product=self.pillar, owner=self.owner,
1968 information_type=InformationType.PROPRIETARY)1972 information_type=InformationType.PROPRIETARY)
@@ -1971,6 +1975,9 @@
1971 self.branch.subscribe(1975 self.branch.subscribe(
1972 self.grantee, BranchSubscriptionNotificationLevel.NOEMAIL,1976 self.grantee, BranchSubscriptionNotificationLevel.NOEMAIL,
1973 None, CodeReviewNotificationLevel.NOEMAIL, self.owner)1977 None, CodeReviewNotificationLevel.NOEMAIL, self.owner)
1978 # XXX cjwatson 2015-02-05: subscribe to Git repository when implemented
1979 getUtility(IService, 'sharing').ensureAccessGrants(
1980 [self.grantee], self.grantor, gitrepositories=[self.gitrepository])
1974 getUtility(IService, 'sharing').ensureAccessGrants(1981 getUtility(IService, 'sharing').ensureAccessGrants(
1975 [self.grantee], self.grantor, specifications=[self.spec])1982 [self.grantee], self.grantor, specifications=[self.spec])
1976 transaction.commit()1983 transaction.commit()
@@ -2094,6 +2101,16 @@
2094 self.assertEqual(1, len(branches))2101 self.assertEqual(1, len(branches))
2095 self.assertEqual(branches[0].unique_name, self.branch.unique_name)2102 self.assertEqual(branches[0].unique_name, self.branch.unique_name)
20962103
2104 def test_getSharedGitRepositories(self):
2105 # Test the exported getSharedGitRepositories() method.
2106 ws_pillar = ws_object(self.launchpad, self.pillar)
2107 ws_grantee = ws_object(self.launchpad, self.grantee)
2108 gitrepositories = self.service.getSharedGitRepositories(
2109 pillar=ws_pillar, person=ws_grantee)
2110 self.assertEqual(1, len(gitrepositories))
2111 self.assertEqual(
2112 gitrepositories[0].unique_name, self.gitrepository.unique_name)
2113
2097 def test_getSharedSpecifications(self):2114 def test_getSharedSpecifications(self):
2098 # Test the exported getSharedSpecifications() method.2115 # Test the exported getSharedSpecifications() method.
2099 ws_pillar = ws_object(self.launchpad, self.pillar)2116 ws_pillar = ws_object(self.launchpad, self.pillar)
21002117
=== modified file 'lib/lp/services/webservice/wadl-to-refhtml.xsl'
--- lib/lp/services/webservice/wadl-to-refhtml.xsl 2013-09-18 06:34:44 +0000
+++ lib/lp/services/webservice/wadl-to-refhtml.xsl 2015-03-06 16:33:15 +0000
@@ -168,6 +168,7 @@
168 <xsl:when test="168 <xsl:when test="
169 @id = 'bug_link_target'169 @id = 'bug_link_target'
170 or @id = 'bug_target'170 or @id = 'bug_target'
171 or @id = 'git_target'
171 or @id = 'has_bugs'172 or @id = 'has_bugs'
172 or @id = 'has_milestones'173 or @id = 'has_milestones'
173 or @id = 'object_with_translation_imports'174 or @id = 'object_with_translation_imports'
@@ -309,6 +310,28 @@
309 <xsl:text>/+email/</xsl:text>310 <xsl:text>/+email/</xsl:text>
310 <var>&lt;email&gt;</var>311 <var>&lt;email&gt;</var>
311 </xsl:when>312 </xsl:when>
313 <xsl:when test="@id = 'git_repository'">
314 <xsl:text>/~</xsl:text>
315 <var>&lt;person.name&gt;</var>
316 <xsl:text>/</xsl:text>
317 <var>&lt;project.name&gt;</var>
318 <xsl:text>/+git/</xsl:text>
319 <var>&lt;repository.name&gt;</var>
320 or
321 <xsl:text>/~</xsl:text>
322 <var>&lt;person.name&gt;</var>
323 <xsl:text>/</xsl:text>
324 <var>&lt;distribution.name&gt;</var>
325 <xsl:text>/+source/</xsl:text>
326 <var>&lt;source_package.name&gt;</var>
327 <xsl:text>/+git/</xsl:text>
328 <var>&lt;repository.name&gt;</var>
329 or
330 <xsl:text>/~</xsl:text>
331 <var>&lt;person.name&gt;</var>
332 <xsl:text>/+git/</xsl:text>
333 <var>&lt;repository.name&gt;</var>
334 </xsl:when>
312 <xsl:when test="@id = 'gpg_key'">335 <xsl:when test="@id = 'gpg_key'">
313 <xsl:text>/</xsl:text>336 <xsl:text>/</xsl:text>
314 <var>&lt;person.name&gt;</var>337 <var>&lt;person.name&gt;</var>