launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #18057
Re: [Merge] lp:~cjwatson/launchpad/git-webservice into lp:launchpad
Review: Approve code
Diff comments:
> === 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-05 16:39:54 +0000
> @@ -102,6 +102,7 @@
> from lp.code.interfaces.branchlookup import IBranchLookup
> from lp.code.interfaces.codehosting import IBazaarApplication
> from lp.code.interfaces.codeimport import ICodeImportSet
> +from lp.code.interfaces.gitrepository import IGitRepositorySet
> from lp.hardwaredb.interfaces.hwdb import IHWDBApplication
> from lp.layers import WebServiceLayer
> from lp.registry.interfaces.announcement import IAnnouncementSet
> @@ -783,6 +784,7 @@
> 'codeofconduct': ICodeOfConductSet,
> '+countries': ICountrySet,
> 'distros': IDistributionSet,
> + '+git': IGitRepositorySet,
> '+hwdb': IHWDBApplication,
> 'karmaaction': IKarmaActionSet,
> '+imports': ITranslationImportQueue,
>
> === 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-05 16:39:54 +0000
> @@ -13,6 +13,11 @@
> path_expression="string:branches"
> parent_utility="lp.services.webapp.interfaces.ILaunchpadRoot"
> />
> + <browser:url
> + for="lp.code.interfaces.gitrepository.IGitRepositorySet"
> + path_expression="string:+git"
> + parent_utility="lp.services.webapp.interfaces.ILaunchpadRoot"
> + />
> <browser:feeds
> module="lp.code.feed.branch"
> classes="BranchFeed PersonBranchFeed ProductBranchFeed ProjectBranchFeed
>
> === 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-05 16:39:54 +0000
> @@ -17,7 +17,23 @@
>
> import re
>
> +from lazr.restful.declarations import (
> + call_with,
> + collection_default_content,
> + export_as_webservice_collection,
> + export_as_webservice_entry,
> + export_destructor_operation,
> + export_read_operation,
> + export_write_operation,
> + exported,
> + operation_for_version,
> + operation_parameters,
> + operation_returns_collection_of,
> + operation_returns_entry,
> + REQUEST_USER,
> + )
> from lazr.restful.fields import Reference
> +from lazr.restful.interface import copy_field
> from zope.component import getUtility
> from zope.interface import (
> Attribute,
> @@ -40,6 +56,7 @@
> from lp.registry.interfaces.distributionsourcepackage import (
> IDistributionSourcePackage,
> )
> +from lp.registry.interfaces.person import IPerson
> from lp.registry.interfaces.persondistributionsourcepackage import (
> IPersonDistributionSourcePackageFactory,
> )
> @@ -97,68 +114,70 @@
>
> id = Int(title=_("ID"), readonly=True, required=True)
>
> - date_created = Datetime(
> - title=_("Date created"), required=True, readonly=True)
> -
> - date_last_modified = Datetime(
> - title=_("Date last modified"), required=True, readonly=True)
> -
> - registrant = PublicPersonChoice(
> + date_created = exported(Datetime(
> + title=_("Date created"), required=True, readonly=True))
> +
> + date_last_modified = exported(Datetime(
> + title=_("Date last modified"), required=True, readonly=True))
> +
> + registrant = exported(PublicPersonChoice(
> title=_("Registrant"), required=True, readonly=True,
> vocabulary="ValidPersonOrTeam",
> - description=_("The person who registered this Git repository."))
> + description=_("The person who registered this Git repository.")))
>
> - owner = PersonChoice(
> + owner = exported(PersonChoice(
> title=_("Owner"), required=True, readonly=False,
> vocabulary="AllUserTeamsParticipationPlusSelf",
> description=_(
> "The owner of this Git repository. This controls who can modify "
> - "the repository."))
> + "the repository.")))
Should this be readonly=True, since it needs to go through namespace methods?
>
> - target = Reference(
> - title=_("Target"), required=True, readonly=True,
> - schema=IHasGitRepositories,
> - description=_("The target of the repository."))
> + target = exported(
> + Reference(
> + title=_("Target"), required=True, readonly=True,
> + schema=IHasGitRepositories,
> + description=_("The target of the repository.")),
> + as_of="devel")
>
> namespace = Attribute(
> "The namespace of this repository, as an `IGitNamespace`.")
>
> - information_type = Choice(
> + information_type = exported(Choice(
> title=_("Information type"), vocabulary=InformationType,
> required=True, readonly=True, default=InformationType.PUBLIC,
> description=_(
> - "The type of information contained in this repository."))
> + "The type of information contained in this repository.")))
>
> - owner_default = Bool(
> + owner_default = exported(Bool(
> title=_("Owner default"), required=True, readonly=True,
> description=_(
> "Whether this repository is the default for its owner and "
> - "target."))
> + "target.")))
>
> - target_default = Bool(
> + target_default = exported(Bool(
> title=_("Target default"), required=True, readonly=True,
> description=_(
> - "Whether this repository is the default for its target."))
> + "Whether this repository is the default for its target.")))
>
> - unique_name = Text(
> + unique_name = exported(Text(
> title=_("Unique name"), readonly=True,
> description=_(
> "Unique name of the repository, including the owner and project "
> - "names."))
> + "names.")))
>
> - display_name = Text(
> + display_name = exported(Text(
> title=_("Display name"), readonly=True,
> - description=_("Display name of the repository."))
> + description=_("Display name of the repository.")))
>
> shortened_path = Attribute(
> "The shortest reasonable version of the path to this repository.")
>
> - git_identity = Text(
> + git_identity = exported(Text(
> title=_("Git identity"), readonly=True,
> description=_(
> "If this is the default repository for some target, then this is "
> "'lp:' plus a shortcut version of the path via that target. "
> - "Otherwise it is simply 'lp:' plus the unique name."))
> + "Otherwise it is simply 'lp:' plus the unique name.")))
>
> def setOwnerDefault(value):
> """Set whether this repository is the default for its owner-target.
> @@ -244,17 +263,23 @@
>
> # XXX cjwatson 2015-01-29: Add some advice about default repository
> # naming.
> - name = TextLine(
> + name = exported(TextLine(
> title=_("Name"), required=True,
> constraint=git_repository_name_validator,
> description=_(
> "The repository name. Keep very short, unique, and descriptive, "
> - "because it will be used in URLs."))
> + "because it will be used in URLs.")))
Similar read-onlyness question to the owner field.
>
>
> class IGitRepositoryModerate(Interface):
> """IGitRepository methods that can be called by more than one community."""
>
> + @operation_parameters(
> + information_type=copy_field(IGitRepositoryView["information_type"]),
> + )
> + @call_with(user=REQUEST_USER)
> + @export_write_operation()
> + @operation_for_version("devel")
> def transitionToInformationType(information_type, user,
> verify_policy=True):
> """Set the information type for this repository.
> @@ -269,12 +294,29 @@
> class IGitRepositoryEdit(Interface):
> """IGitRepository methods that require launchpad.Edit permission."""
>
> + @call_with(user=REQUEST_USER)
> + @operation_parameters(
> + new_owner=Reference(
> + title=_("The new owner of the repository."), schema=IPerson))
> + @export_write_operation()
> + @operation_for_version("devel")
> def setOwner(new_owner, user):
> """Set the owner of the repository to be `new_owner`."""
>
> + @call_with(user=REQUEST_USER)
> + @operation_parameters(
> + target=Reference(
> + title=_(
> + "The project, distribution source package, or person the "
> + "repository belongs to."),
> + schema=IHasGitRepositories, required=True))
> + @export_write_operation()
> + @operation_for_version("devel")
> def setTarget(target, user):
> """Set the target of the repository."""
Should transitionToInformationType, setOwner and setTarget be @mutator_for their corresponding attributes?
>
> + @export_destructor_operation()
> + @operation_for_version("devel")
> def destroySelf():
> """Delete the specified repository."""
>
> @@ -283,14 +325,22 @@
> IGitRepositoryModerate, IGitRepositoryEdit):
> """A Git repository."""
>
> - private = Bool(
> + # Mark repositories as exported entries for the Launchpad API.
> + # XXX cjwatson 2015-01-19 bug=760849: "beta" is a lie to get WADL
> + # generation working. Individual attributes must set their version to
> + # "devel".
> + export_as_webservice_entry(plural_name="git_repositories", as_of="beta")
> +
> + private = exported(Bool(
> title=_("Private"), required=False, readonly=True,
> - description=_("This repository is visible only to its subscribers."))
> + description=_("This repository is visible only to its subscribers.")))
>
>
> class IGitRepositorySet(Interface):
> """Interface representing the set of Git repositories."""
>
> + export_as_webservice_collection(IGitRepository)
> +
> def new(registrant, owner, target, name, information_type=None,
> date_created=None):
> """Create a Git repository and return it.
> @@ -306,6 +356,12 @@
> """
>
> # Marker for references to Git URL layouts: ##GITNAMESPACE##
> + @call_with(user=REQUEST_USER)
> + @operation_parameters(
> + path=TextLine(title=_("Repository path"), required=True))
> + @operation_returns_entry(IGitRepository)
> + @export_read_operation()
> + @operation_for_version("devel")
> def getByPath(user, path):
> """Find a repository by its path.
>
> @@ -324,6 +380,13 @@
> Return None if no match was found.
> """
>
> + @call_with(user=REQUEST_USER)
> + @operation_parameters(
> + target=Reference(
> + title=_("Target"), required=True, schema=IHasGitRepositories))
> + @operation_returns_collection_of(IGitRepository)
> + @export_read_operation()
> + @operation_for_version("devel")
> def getRepositories(user, target):
> """Get all repositories for a target.
>
> @@ -334,6 +397,12 @@
> :return: A collection of `IGitRepository` objects.
> """
>
> + @operation_parameters(
> + target=Reference(
> + title=_("Target"), required=True, schema=IHasGitRepositories))
> + @operation_returns_entry(IGitRepository)
> + @export_read_operation()
> + @operation_for_version("devel")
> def getDefaultRepository(target):
> """Get the default repository for a target.
>
> @@ -343,6 +412,13 @@
> :return: An `IGitRepository`, or None.
> """
>
> + @operation_parameters(
> + owner=Reference(title=_("Owner"), required=True, schema=IPerson),
> + target=Reference(
> + title=_("Target"), required=True, schema=IHasGitRepositories))
> + @operation_returns_entry(IGitRepository)
> + @export_read_operation()
> + @operation_for_version("devel")
> def getDefaultRepositoryForOwner(owner, target):
> """Get a person's default repository for a target.
>
> @@ -353,6 +429,13 @@
> :return: An `IGitRepository`, or None.
> """
>
> + @operation_parameters(
> + target=Reference(
> + title=_("Target"), required=True, schema=IHasGitRepositories),
> + repository=Reference(
> + title=_("Git repository"), required=False, schema=IGitRepository))
> + @export_write_operation()
> + @operation_for_version("devel")
> def setDefaultRepository(target, repository):
> """Set the default repository for a target.
>
> @@ -363,6 +446,14 @@
> :raises GitTargetError: if `target` is an `IPerson`.
> """
>
> + @operation_parameters(
> + owner=Reference(title=_("Owner"), required=True, schema=IPerson),
> + target=Reference(
> + title=_("Target"), required=True, schema=IHasGitRepositories),
> + repository=Reference(
> + title=_("Git repository"), required=False, schema=IGitRepository))
> + @export_write_operation()
> + @operation_for_version("devel")
> def setDefaultRepositoryForOwner(owner, target, repository):
> """Set a person's default repository for a target.
>
> @@ -374,6 +465,7 @@
> :raises GitTargetError: if `target` is an `IPerson`.
> """
>
> + @collection_default_content()
> def empty_list():
> """Return an empty collection of repositories.
>
>
> === 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-05 16:39:54 +0000
> @@ -9,6 +9,7 @@
> 'IHasGitRepositories',
> ]
>
> +from lazr.restful.declarations import export_as_webservice_entry
> from zope.interface import Interface
>
>
> @@ -18,3 +19,6 @@
> A project contains Git repositories, a source package on a distribution
> contains branches, and a person contains "personal" branches.
> """
> +
> + export_as_webservice_entry(
> + singular_name="git_target", plural_name="git_targets", as_of="devel")
>
> === 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-05 16:39:54 +0000
> @@ -25,6 +25,9 @@
> 'ICodeReviewComment',
> 'ICodeReviewVoteReference',
> 'IDiff',
> + 'IGitRepository',
> + 'IGitRepositorySet',
> + 'IHasGitRepositories',
> 'IPreviewDiff',
> 'ISourcePackageRecipe',
> 'ISourcePackageRecipeBuild',
> @@ -57,6 +60,11 @@
> IDiff,
> IPreviewDiff,
> )
> +from lp.code.interfaces.gitrepository import (
> + IGitRepository,
> + IGitRepositorySet,
> + )
> +from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
> from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
> from lp.code.interfaces.sourcepackagerecipebuild import (
> ISourcePackageRecipeBuild,
>
> === 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-05 16:39:54 +0000
> @@ -54,8 +54,11 @@
> from lp.services.database.constants import UTC_NOW
> from lp.services.features.testing import FeatureFixture
> from lp.services.webapp.authorization import check_permission
> +from lp.services.webapp.interfaces import OAuthPermission
> from lp.testing import (
> admin_logged_in,
> + ANONYMOUS,
> + api_url,
> celebrity_logged_in,
> person_logged_in,
> TestCaseWithFactory,
> @@ -65,6 +68,7 @@
> DatabaseFunctionalLayer,
> ZopelessDatabaseLayer,
> )
> +from lp.testing.pages import webservice_for_person
>
>
> class TestGitRepositoryFeatureFlag(TestCaseWithFactory):
> @@ -858,3 +862,64 @@
> TestGitRepositorySetDefaultsOwnerMixin,
> TestGitRepositorySetDefaultsPackage):
> pass
> +
> +
> +class TestWebservice(TestCaseWithFactory):
> + """Tests for the webservice."""
> +
> + layer = DatabaseFunctionalLayer
> +
> + def setUp(self):
> + super(TestWebservice, self).setUp()
> + self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
> +
> + def test_getRepositories_project(self):
> + project_db = self.factory.makeProduct()
> + repository_db = self.factory.makeGitRepository(target=project_db)
> + webservice = webservice_for_person(
> + repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
> + webservice.default_api_version = "devel"
> + with person_logged_in(ANONYMOUS):
> + repository_url = api_url(repository_db)
> + owner_url = api_url(repository_db.owner)
> + project_url = api_url(project_db)
> + response = webservice.named_get(
> + "/+git", "getRepositories", user=owner_url, target=project_url)
> + self.assertEqual(200, response.status)
> + self.assertEqual(
> + [webservice.getAbsoluteUrl(repository_url)],
> + [entry["self_link"] for entry in response.jsonBody()["entries"]])
> +
> + def test_getRepositories_package(self):
> + dsp_db = self.factory.makeDistributionSourcePackage()
> + repository_db = self.factory.makeGitRepository(target=dsp_db)
> + webservice = webservice_for_person(
> + repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
> + webservice.default_api_version = "devel"
> + with person_logged_in(ANONYMOUS):
> + repository_url = api_url(repository_db)
> + owner_url = api_url(repository_db.owner)
> + dsp_url = api_url(dsp_db)
> + response = webservice.named_get(
> + "/+git", "getRepositories", user=owner_url, target=dsp_url)
> + self.assertEqual(200, response.status)
> + self.assertEqual(
> + [webservice.getAbsoluteUrl(repository_url)],
> + [entry["self_link"] for entry in response.jsonBody()["entries"]])
> +
> + def test_getRepositories_personal(self):
> + owner_db = self.factory.makePerson()
> + repository_db = self.factory.makeGitRepository(
> + owner=owner_db, target=owner_db)
> + webservice = webservice_for_person(
> + owner_db, permission=OAuthPermission.WRITE_PUBLIC)
> + webservice.default_api_version = "devel"
> + with person_logged_in(ANONYMOUS):
> + repository_url = api_url(repository_db)
> + owner_url = api_url(owner_db)
> + response = webservice.named_get(
> + "/+git", "getRepositories", user=owner_url, target=owner_url)
> + self.assertEqual(200, response.status)
> + self.assertEqual(
> + [webservice.getAbsoluteUrl(repository_url)],
> + [entry["self_link"] for entry in response.jsonBody()["entries"]])
>
> === 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-05 16:39:54 +0000
> @@ -18,6 +18,7 @@
> operation_for_version,
> operation_parameters,
> operation_returns_collection_of,
> + rename_parameters_as,
> REQUEST_USER,
> )
> from lazr.restful.fields import Reference
> @@ -33,6 +34,7 @@
> from lp.blueprints.interfaces.specification import ISpecification
> from lp.bugs.interfaces.bug import IBug
> from lp.code.interfaces.branch import IBranch
> +from lp.code.interfaces.gitrepository import IGitRepository
> from lp.registry.enums import (
> BranchSharingPolicy,
> BugSharingPolicy,
> @@ -148,6 +150,13 @@
> :return: a collection of branches
> """
>
> + @export_read_operation()
> + @call_with(user=REQUEST_USER)
> + @operation_parameters(
> + pillar=Reference(IPillar, title=_('Pillar'), required=True),
> + person=Reference(IPerson, title=_('Person'), required=True))
> + @operation_returns_collection_of(IGitRepository)
> + @operation_for_version('devel')
> def getSharedGitRepositories(pillar, person, user):
> """Return the Git repositories shared between the pillar and person.
>
> @@ -312,6 +321,7 @@
>
> @export_write_operation()
> @call_with(user=REQUEST_USER)
> + @rename_parameters_as(gitrepositories='git_repositories')
> @operation_parameters(
> pillar=Reference(IPillar, title=_('Pillar'), required=True),
> grantee=Reference(IPerson, title=_('Grantee'), required=True),
> @@ -319,6 +329,9 @@
> Reference(schema=IBug), title=_('Bugs'), required=False),
> branches=List(
> Reference(schema=IBranch), title=_('Branches'), required=False),
> + gitrepositories=List(
> + Reference(schema=IGitRepository),
> + title=_('Git repositories'), required=False),
> specifications=List(
> Reference(schema=ISpecification), title=_('Specifications'),
> required=False))
> @@ -338,13 +351,17 @@
>
> @export_write_operation()
> @call_with(user=REQUEST_USER)
> + @rename_parameters_as(gitrepositories='git_repositories')
> @operation_parameters(
> grantees=List(
> Reference(IPerson, title=_('Grantee'), required=True)),
> bugs=List(
> Reference(schema=IBug), title=_('Bugs'), required=False),
> branches=List(
> - Reference(schema=IBranch), title=_('Branches'), required=False))
> + Reference(schema=IBranch), title=_('Branches'), required=False),
> + gitrepositories=List(
> + Reference(schema=IGitRepository),
> + title=_('Git repositories'), required=False))
> @operation_for_version('devel')
> def ensureAccessGrants(grantees, user, bugs=None, branches=None,
> gitrepositories=None, specifications=None):
>
> === 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-05 16:39:54 +0000
> @@ -1949,6 +1949,7 @@
>
> def setUp(self):
> super(ApiTestMixin, self).setUp()
> + self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
> self.owner = self.factory.makePerson(name='thundercat')
> self.pillar = self.factory.makeProduct(
> owner=self.owner, specification_sharing_policy=(
> @@ -1963,6 +1964,9 @@
> self.branch = self.factory.makeBranch(
> owner=self.owner, product=self.pillar,
> information_type=InformationType.PRIVATESECURITY)
> + self.gitrepository = self.factory.makeGitRepository(
> + owner=self.owner, target=self.pillar,
> + information_type=InformationType.PRIVATESECURITY)
> self.spec = self.factory.makeSpecification(
> product=self.pillar, owner=self.owner,
> information_type=InformationType.PROPRIETARY)
> @@ -1971,6 +1975,9 @@
> self.branch.subscribe(
> self.grantee, BranchSubscriptionNotificationLevel.NOEMAIL,
> None, CodeReviewNotificationLevel.NOEMAIL, self.owner)
> + # XXX cjwatson 2015-02-05: subscribe to Git repository when implemented
> + getUtility(IService, 'sharing').ensureAccessGrants(
> + [self.grantee], self.grantor, gitrepositories=[self.gitrepository])
> getUtility(IService, 'sharing').ensureAccessGrants(
> [self.grantee], self.grantor, specifications=[self.spec])
> transaction.commit()
> @@ -2094,6 +2101,16 @@
> self.assertEqual(1, len(branches))
> self.assertEqual(branches[0].unique_name, self.branch.unique_name)
>
> + def test_getSharedGitRepositories(self):
> + # Test the exported getSharedGitRepositories() method.
> + ws_pillar = ws_object(self.launchpad, self.pillar)
> + ws_grantee = ws_object(self.launchpad, self.grantee)
> + gitrepositories = self.service.getSharedGitRepositories(
> + pillar=ws_pillar, person=ws_grantee)
> + self.assertEqual(1, len(gitrepositories))
> + self.assertEqual(
> + gitrepositories[0].unique_name, self.gitrepository.unique_name)
> +
> def test_getSharedSpecifications(self):
> # Test the exported getSharedSpecifications() method.
> ws_pillar = ws_object(self.launchpad, self.pillar)
>
> === 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-05 16:39:54 +0000
> @@ -168,6 +168,7 @@
> <xsl:when test="
> @id = 'bug_link_target'
> or @id = 'bug_target'
> + or @id = 'git_target'
> or @id = 'has_bugs'
> or @id = 'has_milestones'
> or @id = 'object_with_translation_imports'
> @@ -309,6 +310,28 @@
> <xsl:text>/+email/</xsl:text>
> <var><email></var>
> </xsl:when>
> + <xsl:when test="@id = 'git_repository'">
> + <xsl:text>/~</xsl:text>
> + <var><person.name></var>
> + <xsl:text>/</xsl:text>
> + <var><project.name></var>
> + <xsl:text>/+git/</xsl:text>
> + <var><repository.name></var>
> + or
> + <xsl:text>/~</xsl:text>
> + <var><person.name></var>
> + <xsl:text>/</xsl:text>
> + <var><distribution.name></var>
> + <xsl:text>/+source/</xsl:text>
> + <var><source_package.name></var>
> + <xsl:text>/+git/</xsl:text>
> + <var><repository.name></var>
> + or
> + <xsl:text>/~</xsl:text>
> + <var><person.name></var>
> + <xsl:text>/+git/</xsl:text>
> + <var><repository.name></var>
> + </xsl:when>
> <xsl:when test="@id = 'gpg_key'">
> <xsl:text>/</xsl:text>
> <var><person.name></var>
>
--
https://code.launchpad.net/~cjwatson/launchpad/git-webservice/+merge/251978
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.
References