← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/git-webservice-ref into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/git-webservice-ref into lp:launchpad.

Commit message:
Export GitRef and GitRepository.{refs,branches}.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/git-webservice-ref/+merge/256014

Export GitRef and GitRepository.{refs,branches}.  This is a prerequisite for merge proposals, which will need to e.g. be able to call createMergeProposal on a GitRef.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/git-webservice-ref into lp:launchpad.
=== modified file 'lib/lp/_schema_circular_imports.py'
--- lib/lp/_schema_circular_imports.py	2015-04-08 10:35:22 +0000
+++ lib/lp/_schema_circular_imports.py	2015-04-13 19:06:51 +0000
@@ -69,6 +69,8 @@
 from lp.code.interfaces.codereviewcomment import ICodeReviewComment
 from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
 from lp.code.interfaces.diff import IPreviewDiff
+from lp.code.interfaces.gitref import IGitRef
+from lp.code.interfaces.gitrepository import IGitRepository
 from lp.code.interfaces.hasbranches import (
     IHasBranches,
     IHasCodeImports,
@@ -559,6 +561,13 @@
 # IDistroArchSeries
 patch_reference_property(IDistroArchSeries, 'main_archive', IArchive)
 
+# IGitRef
+patch_reference_property(IGitRef, 'repository', IGitRepository)
+
+# IGitRepository
+patch_collection_property(IGitRepository, 'branches', IGitRef)
+patch_collection_property(IGitRepository, 'refs', IGitRef)
+
 # ILiveFSFile
 patch_reference_property(ILiveFSFile, 'livefsbuild', ILiveFSBuild)
 

=== modified file 'lib/lp/code/interfaces/gitref.py'
--- lib/lp/code/interfaces/gitref.py	2015-03-24 15:11:28 +0000
+++ lib/lp/code/interfaces/gitref.py	2015-04-13 19:06:51 +0000
@@ -10,6 +10,11 @@
     'IGitRefBatchNavigator',
     ]
 
+from lazr.restful.declarations import (
+    export_as_webservice_entry,
+    exported,
+    )
+from lazr.restful.fields import ReferenceChoice
 from zope.interface import (
     Attribute,
     Interface,
@@ -29,18 +34,28 @@
 class IGitRef(Interface):
     """A reference in a Git repository."""
 
-    repository = Attribute("The Git repository containing this reference.")
-
-    path = TextLine(
+    # 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(as_of="beta")
+
+    repository = exported(ReferenceChoice(
+        title=_("Repository"), required=True, readonly=True,
+        vocabulary="GitRepository",
+        # Really IGitRepository, patched in _schema_circular_imports.py.
+        schema=Interface,
+        description=_("The Git repository containing this reference.")))
+
+    path = exported(TextLine(
         title=_("Path"), required=True, readonly=True,
         description=_(
-            "The full path of this reference, e.g. refs/heads/master."))
+            "The full path of this reference, e.g. refs/heads/master.")))
 
-    commit_sha1 = TextLine(
+    commit_sha1 = exported(TextLine(
         title=_("Commit SHA-1"), required=True, readonly=True,
         description=_(
             "The full SHA-1 object name of the commit object referenced by "
-            "this reference."))
+            "this reference.")))
 
     object_type = Choice(
         title=_("Object type"), required=True, readonly=True,

=== modified file 'lib/lp/code/interfaces/gitrepository.py'
--- lib/lp/code/interfaces/gitrepository.py	2015-03-20 14:54:23 +0000
+++ lib/lp/code/interfaces/gitrepository.py	2015-04-13 19:06:51 +0000
@@ -33,7 +33,10 @@
     operation_returns_entry,
     REQUEST_USER,
     )
-from lazr.restful.fields import Reference
+from lazr.restful.fields import (
+    CollectionField,
+    Reference,
+    )
 from lazr.restful.interface import copy_field
 from zope.component import getUtility
 from zope.interface import (
@@ -186,9 +189,17 @@
             "'lp:' plus a shortcut version of the path via that target.  "
             "Otherwise it is simply 'lp:' plus the unique name.")))
 
-    refs = Attribute("The references present in this repository.")
+    refs = exported(CollectionField(
+        title=_("The references present in this repository."),
+        readonly=True,
+        # Really IGitRef, patched in _schema_circular_imports.py.
+        value_type=Reference(Interface)))
 
-    branches = Attribute("The branch references present in this repository.")
+    branches = exported(CollectionField(
+        title=_("The branch references present in this repository."),
+        readonly=True,
+        # Really IGitRef, patched in _schema_circular_imports.py.
+        value_type=Reference(Interface)))
 
     def getRefByPath(path):
         """Look up a single reference in this repository by path.

=== modified file 'lib/lp/code/interfaces/webservice.py'
--- lib/lp/code/interfaces/webservice.py	2015-03-05 16:23:26 +0000
+++ lib/lp/code/interfaces/webservice.py	2015-04-13 19:06:51 +0000
@@ -25,6 +25,7 @@
     'ICodeReviewComment',
     'ICodeReviewVoteReference',
     'IDiff',
+    'IGitRef',
     'IGitRepository',
     'IGitRepositorySet',
     'IHasGitRepositories',
@@ -60,6 +61,7 @@
     IDiff,
     IPreviewDiff,
     )
+from lp.code.interfaces.gitref import IGitRef
 from lp.code.interfaces.gitrepository import (
     IGitRepository,
     IGitRepositorySet,

=== modified file 'lib/lp/code/model/tests/test_gitref.py'
--- lib/lp/code/model/tests/test_gitref.py	2015-03-13 14:15:24 +0000
+++ lib/lp/code/model/tests/test_gitref.py	2015-04-13 19:06:51 +0000
@@ -5,10 +5,21 @@
 
 __metaclass__ = type
 
+import hashlib
+
+from testtools.matchers import EndsWith
+
 from lp.code.interfaces.gitrepository import GIT_FEATURE_FLAG
 from lp.services.features.testing import FeatureFixture
-from lp.testing import TestCaseWithFactory
+from lp.services.webapp.interfaces import OAuthPermission
+from lp.testing import (
+    ANONYMOUS,
+    api_url,
+    person_logged_in,
+    TestCaseWithFactory,
+    )
 from lp.testing.layers import DatabaseFunctionalLayer
+from lp.testing.pages import webservice_for_person
 
 
 class TestGitRef(TestCaseWithFactory):
@@ -22,3 +33,27 @@
         self.assertEqual(
             ["master", "people/foo/bar"],
             [ref.display_name for ref in (master, personal)])
+
+
+class TestGitRefWebservice(TestCaseWithFactory):
+    """Tests for the webservice."""
+
+    layer = DatabaseFunctionalLayer
+
+    def test_attributes(self):
+        self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
+        [master] = self.factory.makeGitRefs(paths=[u"refs/heads/master"])
+        webservice = webservice_for_person(
+            master.repository.owner, permission=OAuthPermission.READ_PUBLIC)
+        webservice.default_api_version = "devel"
+        with person_logged_in(ANONYMOUS):
+            repository_url = api_url(master.repository)
+            master_url = api_url(master)
+        response = webservice.get(master_url)
+        self.assertEqual(200, response.status)
+        result = response.jsonBody()
+        self.assertThat(result["repository_link"], EndsWith(repository_url))
+        self.assertEqual(u"refs/heads/master", result["path"])
+        self.assertEqual(
+            unicode(hashlib.sha1(u"refs/heads/master").hexdigest()),
+            result["commit_sha1"])

=== modified file 'lib/lp/code/vocabularies/configure.zcml'
--- lib/lp/code/vocabularies/configure.zcml	2012-09-05 05:08:26 +0000
+++ lib/lp/code/vocabularies/configure.zcml	2015-04-13 19:06:51 +0000
@@ -55,4 +55,15 @@
     <allow interface="lp.services.webapp.vocabulary.IHugeVocabulary"/>
   </class>
 
+  <securedutility
+    name="GitRepository"
+    component=".gitrepository.GitRepositoryVocabulary"
+    provides="zope.schema.interfaces.IVocabularyFactory">
+    <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
+  </securedutility>
+
+  <class class=".gitrepository.GitRepositoryVocabulary">
+    <allow interface="lp.services.webapp.vocabulary.IHugeVocabulary"/>
+  </class>
+
 </configure>

=== added file 'lib/lp/code/vocabularies/gitrepository.py'
--- lib/lp/code/vocabularies/gitrepository.py	1970-01-01 00:00:00 +0000
+++ lib/lp/code/vocabularies/gitrepository.py	2015-04-13 19:06:51 +0000
@@ -0,0 +1,64 @@
+# Copyright 2015 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Vocabularies that contain Git repositories."""
+
+__metaclass__ = type
+
+__all__ = [
+    'GitRepositoryVocabulary',
+    ]
+
+from zope.component import getUtility
+from zope.interface import implements
+from zope.schema.vocabulary import SimpleTerm
+
+from lp.code.interfaces.gitcollection import IAllGitRepositories
+from lp.code.model.gitrepository import GitRepository
+from lp.services.webapp.interfaces import ILaunchBag
+from lp.services.webapp.vocabulary import (
+    CountableIterator,
+    IHugeVocabulary,
+    SQLObjectVocabularyBase,
+    )
+
+
+class GitRepositoryVocabulary(SQLObjectVocabularyBase):
+    """A vocabulary for searching Git repositories."""
+
+    implements(IHugeVocabulary)
+
+    _table = GitRepository
+    _orderBy = ['name', 'id']
+    displayname = 'Select a Git repository'
+    step_title = 'Search'
+
+    def toTerm(self, repository):
+        """The display should include the URL if there is one."""
+        return SimpleTerm(
+            repository, repository.unique_name, repository.unique_name)
+
+    def getTermByToken(self, token):
+        """See `IVocabularyTokenized`."""
+        search_results = self.searchForTerms(token)
+        if search_results.count() == 1:
+            return iter(search_results).next()
+        raise LookupError(token)
+
+    def searchForTerms(self, query=None, vocab_filter=None):
+        """See `IHugeVocabulary`."""
+        user = getUtility(ILaunchBag).user
+        collection = self._getCollection().visibleByUser(user)
+        if query is None:
+            repositories = collection.getRepositories(eager_load=False)
+        else:
+            repositories = collection.search(query)
+        return CountableIterator(
+            repositories.count(), repositories, self.toTerm)
+
+    def __len__(self):
+        """See `IVocabulary`."""
+        return self.search().count()
+
+    def _getCollection(self):
+        return getUtility(IAllGitRepositories)

=== added file 'lib/lp/code/vocabularies/tests/test_gitrepository_vocabularies.py'
--- lib/lp/code/vocabularies/tests/test_gitrepository_vocabularies.py	1970-01-01 00:00:00 +0000
+++ lib/lp/code/vocabularies/tests/test_gitrepository_vocabularies.py	2015-04-13 19:06:51 +0000
@@ -0,0 +1,55 @@
+# Copyright 2015 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test the Git repository vocabularies."""
+
+__metaclass__ = type
+
+from lp.code.interfaces.gitrepository import GIT_FEATURE_FLAG
+from lp.code.vocabularies.gitrepository import GitRepositoryVocabulary
+from lp.services.features.testing import FeatureFixture
+from lp.testing import TestCaseWithFactory
+from lp.testing.layers import DatabaseFunctionalLayer
+
+
+class TestGitRepositoryVocabulary(TestCaseWithFactory):
+    """Test that the GitRepositoryVocabulary behaves as expected."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestGitRepositoryVocabulary, self).setUp()
+        self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
+        self._createRepositories()
+        self.vocab = GitRepositoryVocabulary(context=None)
+
+    def _createRepositories(self):
+        widget = self.factory.makeProduct(name="widget")
+        sprocket = self.factory.makeProduct(name="sprocket")
+        scotty = self.factory.makePerson(name="scotty")
+        self.factory.makeGitRepository(
+            owner=scotty, target=widget, name=u"fizzbuzz")
+        self.factory.makeGitRepository(
+            owner=scotty, target=widget, name=u"mountain")
+        self.factory.makeGitRepository(
+            owner=scotty, target=sprocket, name=u"fizzbuzz")
+
+    def test_fizzbuzzRepositories(self):
+        """Return repositories that match the string 'fizzbuzz'."""
+        results = self.vocab.searchForTerms("fizzbuzz")
+        expected = [
+            u"~scotty/sprocket/+git/fizzbuzz", u"~scotty/widget/+git/fizzbuzz"]
+        repository_names = sorted([repository.token for repository in results])
+        self.assertEqual(expected, repository_names)
+
+    def test_singleQueryResult(self):
+        # If there is a single search result that matches, use that
+        # as the result.
+        term = self.vocab.getTermByToken("mountain")
+        self.assertEqual(
+            "~scotty/widget/+git/mountain", term.value.unique_name)
+
+    def test_multipleQueryResult(self):
+        # If there are more than one search result, a LookupError is still
+        # raised.
+        self.assertRaises(LookupError, self.vocab.getTermByToken, "fizzbuzz")

=== modified file 'lib/lp/services/webservice/wadl-to-refhtml.xsl'
--- lib/lp/services/webservice/wadl-to-refhtml.xsl	2015-04-13 15:03:00 +0000
+++ lib/lp/services/webservice/wadl-to-refhtml.xsl	2015-04-13 19:06:51 +0000
@@ -317,6 +317,34 @@
                 <xsl:text>/+faq/</xsl:text>
                 <var >&lt;faq.id&gt;</var>
             </xsl:when>
+            <xsl:when test="@id = 'git_ref'">
+                <xsl:text>/~</xsl:text>
+                <var>&lt;person.name&gt;</var>
+                <xsl:text>/</xsl:text>
+                <var>&lt;project.name&gt;</var>
+                <xsl:text>/+git/</xsl:text>
+                <var>&lt;repository.name&gt;</var>
+                <xsl:text>/+ref/</xsl:text>
+                <var>&lt;path&gt;</var>
+                or
+                <xsl:text>/~</xsl:text>
+                <var>&lt;person.name&gt;</var>
+                <xsl:text>/</xsl:text>
+                <var>&lt;distribution.name&gt;</var>
+                <xsl:text>/+source/</xsl:text>
+                <var>&lt;source_package.name&gt;</var>
+                <xsl:text>/+git/</xsl:text>
+                <var>&lt;repository.name&gt;</var>
+                <xsl:text>/+ref/</xsl:text>
+                <var>&lt;path&gt;</var>
+                or
+                <xsl:text>/~</xsl:text>
+                <var>&lt;person.name&gt;</var>
+                <xsl:text>/+git/</xsl:text>
+                <var>&lt;repository.name&gt;</var>
+                <xsl:text>/+ref/</xsl:text>
+                <var>&lt;path&gt;</var>
+            </xsl:when>
             <xsl:when test="@id = 'git_repository'">
                 <xsl:text>/~</xsl:text>
                 <var>&lt;person.name&gt;</var>


Follow ups