← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:oci-git-listing into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:oci-git-listing into launchpad:master with ~cjwatson/launchpad:oci-git-owner-default as a prerequisite.

Commit message:
Add git listing views for OCI projects

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1847444 in Launchpad itself: "Support OCI image building"
  https://bugs.launchpad.net/launchpad/+bug/1847444

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/376146
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:oci-git-listing into launchpad:master.
diff --git a/lib/lp/code/browser/configure.zcml b/lib/lp/code/browser/configure.zcml
index cbcd9ea..499a86a 100644
--- a/lib/lp/code/browser/configure.zcml
+++ b/lib/lp/code/browser/configure.zcml
@@ -1047,6 +1047,12 @@
         name="+git"
         template="../templates/gitlisting.pt"/>
     <browser:page
+        for="lp.registry.interfaces.ociproject.IOCIProject"
+        class="lp.code.browser.gitlisting.OCIProjectGitListingView"
+        permission="zope.Public"
+        name="+git"
+        template="../templates/gitlisting.pt"/>
+    <browser:page
         for="lp.registry.interfaces.personproduct.IPersonProduct"
         class="lp.code.browser.gitlisting.PersonTargetGitListingView"
         permission="zope.Public"
@@ -1059,6 +1065,12 @@
         name="+git"
         template="../templates/gitlisting.pt"/>
     <browser:page
+        for="lp.registry.interfaces.personociproject.IPersonOCIProject"
+        class="lp.code.browser.gitlisting.PersonOCIProjectGitListingView"
+        permission="zope.Public"
+        name="+git"
+        template="../templates/gitlisting.pt"/>
+    <browser:page
         for="lp.registry.interfaces.person.IPerson"
         class="lp.code.browser.gitlisting.PlainGitListingView"
         permission="zope.Public"
@@ -1096,6 +1108,16 @@
         name="+code"/>
 
     <browser:defaultView
+        for="lp.registry.interfaces.ociproject.IOCIProject"
+        layer="lp.code.publisher.CodeLayer"
+        name="+code"/>
+
+    <browser:defaultView
+        for="lp.registry.interfaces.personociproject.IPersonOCIProject"
+        layer="lp.code.publisher.CodeLayer"
+        name="+code"/>
+
+    <browser:defaultView
         for="lp.registry.interfaces.distribution.IDistribution"
         layer="lp.code.publisher.CodeLayer"
         name="+code"/>
diff --git a/lib/lp/code/browser/gitlisting.py b/lib/lp/code/browser/gitlisting.py
index 8e51d71..3827f73 100644
--- a/lib/lp/code/browser/gitlisting.py
+++ b/lib/lp/code/browser/gitlisting.py
@@ -28,6 +28,7 @@ from lp.code.interfaces.gitrepository import IGitRepositorySet
 from lp.registry.interfaces.persondistributionsourcepackage import (
     IPersonDistributionSourcePackage,
     )
+from lp.registry.interfaces.personociproject import IPersonOCIProject
 from lp.registry.interfaces.personproduct import IPersonProduct
 from lp.services.config import config
 from lp.services.propertycache import cachedproperty
@@ -143,6 +144,8 @@ class PersonTargetGitListingView(BaseGitListingView):
             return self.context.product
         elif IPersonDistributionSourcePackage.providedBy(self.context):
             return self.context.distro_source_package
+        elif IPersonOCIProject.providedBy(self.context):
+            return self.context.oci_project
         else:
             raise Exception("Unknown context: %r" % self.context)
 
@@ -158,6 +161,12 @@ class PersonTargetGitListingView(BaseGitListingView):
             return None
 
 
+class OCIProjectGitListingView(TargetGitListingView):
+
+    # OCIProject:+branches doesn't exist.
+    show_bzr_link = False
+
+
 class PersonDistributionSourcePackageGitListingView(
         PersonTargetGitListingView):
 
@@ -165,6 +174,12 @@ class PersonDistributionSourcePackageGitListingView(
     show_bzr_link = False
 
 
+class PersonOCIProjectGitListingView(PersonTargetGitListingView):
+
+    # PersonOCIProject:+branches doesn't exist.
+    show_bzr_link = False
+
+
 class PlainGitListingView(BaseGitListingView):
 
     page_title = 'Git'
diff --git a/lib/lp/code/browser/tests/test_gitlisting.py b/lib/lp/code/browser/tests/test_gitlisting.py
index 2552f73..72954ad 100644
--- a/lib/lp/code/browser/tests/test_gitlisting.py
+++ b/lib/lp/code/browser/tests/test_gitlisting.py
@@ -13,6 +13,7 @@ from lp.app.enums import InformationType
 from lp.code.enums import BranchMergeProposalStatus
 from lp.code.interfaces.gitrepository import IGitRepositorySet
 from lp.registry.enums import VCSType
+from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
 from lp.registry.model.persondistributionsourcepackage import (
     PersonDistributionSourcePackage,
     )
@@ -34,6 +35,10 @@ class TestTargetGitListingView:
 
     layer = DatabaseFunctionalLayer
 
+    def setDefaultRepository(self, target, repository):
+        getUtility(IGitRepositorySet).setDefaultRepository(
+            target=target, repository=repository)
+
     def test_rendering(self):
         main_repo = self.factory.makeGitRepository(
             owner=self.owner, target=self.target, name="foo")
@@ -53,8 +58,7 @@ class TestTargetGitListingView:
             target=self.target, name="bar")
 
         with admin_logged_in():
-            getUtility(IGitRepositorySet).setDefaultRepository(
-                target=self.target, repository=main_repo)
+            self.setDefaultRepository(target=self.target, repository=main_repo)
             getUtility(IGitRepositorySet).setDefaultRepositoryForOwner(
                 owner=other_repo.owner, target=self.target,
                 repository=other_repo, user=other_repo.owner)
@@ -115,8 +119,7 @@ class TestTargetGitListingView:
             self.factory.makeGitRefs(other_repo)
 
         with admin_logged_in():
-            getUtility(IGitRepositorySet).setDefaultRepository(
-                target=self.target, repository=main_repo)
+            self.setDefaultRepository(target=self.target, repository=main_repo)
             getUtility(IGitRepositorySet).setDefaultRepositoryForOwner(
                 owner=other_repo.owner, target=self.target,
                 repository=other_repo, user=other_repo.owner)
@@ -155,7 +158,7 @@ class TestTargetGitListingView:
         other_repo = self.factory.makeGitRepository(
             target=self.target, information_type=InformationType.PUBLIC)
         with admin_logged_in():
-            getUtility(IGitRepositorySet).setDefaultRepository(
+            self.setDefaultRepository(
                 target=self.target, repository=invisible_repo)
 
         # An anonymous user can't see the default.
@@ -339,8 +342,7 @@ class TestProductGitListingView(TestTargetGitListingView,
             paths=["refs/heads/master", "refs/heads/1.0", "refs/tags/1.1"])
 
         with admin_logged_in():
-            getUtility(IGitRepositorySet).setDefaultRepository(
-                    target=self.target, repository=main_repo)
+            self.setDefaultRepository(target=self.target, repository=main_repo)
 
         self.factory.makeBranchMergeProposalForGit(
             target_ref=git_refs[0],
@@ -415,6 +417,48 @@ class TestPersonDistributionSourcePackageGitListingView(
         self.assertNotIn('View Bazaar branches', view())
 
 
+class TestOCIProjectGitListingView(
+        TestTargetGitListingView, TestCaseWithFactory):
+
+    def setUp(self):
+        super(TestOCIProjectGitListingView, self).setUp()
+        self.owner = self.factory.makePerson(name="foowner")
+        distro = self.factory.makeDistribution(name="foo", owner=self.owner)
+        self.target = self.factory.makeOCIProject(
+            pillar=distro, ociprojectname="bar")
+        self.target_path = "foo/+oci/bar"
+
+    def setDefaultRepository(self, target, repository):
+        getUtility(IGitRepositorySet).setDefaultRepository(
+            target=target, repository=repository, force_oci=True)
+
+    def test_bzr_link(self):
+        # There's no OCIProject:+branches, nor any ability to create Bazaar
+        # branches for OCI projects.
+        view = create_initialized_view(self.target, '+git')
+        self.assertNotIn('View Bazaar branches', view())
+
+
+class TestPersonOCIProjectGitListingView(
+        TestPersonTargetGitListingView, TestCaseWithFactory):
+
+    def setUp(self):
+        super(TestPersonOCIProjectGitListingView, self).setUp()
+        self.owner = self.factory.makePerson(name="dev")
+        distro = self.factory.makeDistribution(name="foo", owner=self.owner)
+        self.target = self.factory.makeOCIProject(
+            pillar=distro, ociprojectname="bar")
+        self.target_path = "foo/+oci/bar"
+        self.owner_target = getUtility(IPersonOCIProjectFactory).create(
+            self.owner, self.target)
+
+    def test_bzr_link(self):
+        # There's no PersonOCIProject:+branches, nor any ability to create
+        # Bazaar branches for OCI projects.
+        view = create_initialized_view(self.owner_target, '+git')
+        self.assertNotIn('View Bazaar branches', view())
+
+
 class TestPlainGitListingView:
 
     layer = DatabaseFunctionalLayer
diff --git a/lib/lp/code/browser/tests/test_vcslisting.py b/lib/lp/code/browser/tests/test_vcslisting.py
index 2d7741d..127d9d3 100644
--- a/lib/lp/code/browser/tests/test_vcslisting.py
+++ b/lib/lp/code/browser/tests/test_vcslisting.py
@@ -126,6 +126,62 @@ class TestPersonDistributionSourcePackageDefaultVCSView(TestCaseWithFactory):
         self.assertCodeViewClass(VCSType.GIT, PersonTargetGitListingView)
 
 
+class TestOCIProjectDefaultVCSView(TestCaseWithFactory):
+    """Tests that OCIProject:+code delegates to +git.
+
+    This is regardless of the distribution's preferred VCS.  It can't delegate
+    to +branches, as OCIProject:+branches doesn't exist.
+    """
+
+    layer = DatabaseFunctionalLayer
+
+    def assertCodeViewClass(self, vcs, cls):
+        distro = self.factory.makeDistribution(vcs=vcs)
+        oci_project = self.factory.makeOCIProject(pillar=distro)
+        self.assertEqual(vcs, distro.vcs)
+        view = test_traverse(
+            '/%s/+oci/%s/+code' % (distro.name, oci_project.name))[1]
+        self.assertIsInstance(view, cls)
+
+    def test_default_unset(self):
+        self.assertCodeViewClass(None, TargetGitListingView)
+
+    def test_default_bzr(self):
+        self.assertCodeViewClass(VCSType.BZR, TargetGitListingView)
+
+    def test_git(self):
+        self.assertCodeViewClass(VCSType.GIT, TargetGitListingView)
+
+
+class TestPersonOCIProjectDefaultVCSView(TestCaseWithFactory):
+    """Tests that OCIProject:+code delegates to +git.
+
+    This is regardless of the distribution's preferred VCS.  It can't
+    delegate to +branches, as PersonOCIProject:+branches doesn't exist.
+    """
+
+    layer = DatabaseFunctionalLayer
+
+    def assertCodeViewClass(self, vcs, cls):
+        person = self.factory.makePerson()
+        distro = self.factory.makeDistribution(vcs=vcs)
+        oci_project = self.factory.makeOCIProject(pillar=distro)
+        self.assertEqual(vcs, distro.vcs)
+        view = test_traverse(
+            '~%s/%s/+oci/%s/+code'
+            % (person.name, distro.name, oci_project.name))[1]
+        self.assertIsInstance(view, cls)
+
+    def test_default_unset(self):
+        self.assertCodeViewClass(None, PersonTargetGitListingView)
+
+    def test_default_bzr(self):
+        self.assertCodeViewClass(VCSType.BZR, PersonTargetGitListingView)
+
+    def test_git(self):
+        self.assertCodeViewClass(VCSType.GIT, PersonTargetGitListingView)
+
+
 class TestDistributionDefaultVCSView(TestCaseWithFactory):
     """Tests that Distribution:+code delegates to +git or +branches."""
 
diff --git a/lib/lp/code/browser/vcslisting.py b/lib/lp/code/browser/vcslisting.py
index bc76a38..5291234 100644
--- a/lib/lp/code/browser/vcslisting.py
+++ b/lib/lp/code/browser/vcslisting.py
@@ -8,9 +8,11 @@ __metaclass__ = type
 from zope.component import queryMultiAdapter
 
 from lp.registry.enums import VCSType
+from lp.registry.interfaces.ociproject import IOCIProject
 from lp.registry.interfaces.persondistributionsourcepackage import (
     IPersonDistributionSourcePackage,
     )
+from lp.registry.interfaces.personociproject import IPersonOCIProject
 from lp.registry.interfaces.personproduct import IPersonProduct
 from lp.services.webapp import stepto
 
@@ -19,9 +21,14 @@ class TargetDefaultVCSNavigationMixin:
 
     @stepto("+code")
     def traverse_code_view(self):
-        if self.context.pillar.vcs in (VCSType.BZR, None):
+        if IOCIProject.providedBy(self.context):
+            # OCI projects only support Git.
+            vcs = VCSType.GIT
+        else:
+            vcs = self.context.pillar.vcs
+        if vcs in (VCSType.BZR, None):
             view_name = '+branches'
-        elif self.context.pillar.vcs == VCSType.GIT:
+        elif vcs == VCSType.GIT:
             view_name = '+git'
         else:
             raise AssertionError("Unknown VCS")
@@ -37,11 +44,18 @@ class PersonTargetDefaultVCSNavigationMixin:
             target = self.context.product
         elif IPersonDistributionSourcePackage.providedBy(self.context):
             target = self.context.distro_source_package
+        elif IPersonOCIProject.providedBy(self.context):
+            target = self.context.oci_project
         else:
             raise AssertionError("Unknown target: %r" % self.context)
-        if target.pillar.vcs in (VCSType.BZR, None):
+        if IOCIProject.providedBy(target):
+            # OCI projects only support Git.
+            vcs = VCSType.GIT
+        else:
+            vcs = target.pillar.vcs
+        if vcs in (VCSType.BZR, None):
             view_name = '+branches'
-        elif target.pillar.vcs == VCSType.GIT:
+        elif vcs == VCSType.GIT:
             view_name = '+git'
         else:
             raise AssertionError("Unknown VCS")
diff --git a/lib/lp/code/templates/gitlisting.pt b/lib/lp/code/templates/gitlisting.pt
index 8afb6e1..583deef 100644
--- a/lib/lp/code/templates/gitlisting.pt
+++ b/lib/lp/code/templates/gitlisting.pt
@@ -26,7 +26,7 @@
           <span tal:condition="not: view/default_information_type"
             id="privacy-text">
             You can't create new repositories for
-            <tal:name replace="context/displayname"/>.
+            <tal:name replace="context/display_name"/>.
             <tal:sharing-link condition="context/required:launchpad.Edit">
             <br/>This can be fixed by changing the branch sharing policy on the
             <a tal:attributes="href string:${view/target/fmt:url:mainsite}/+sharing">sharing page</a>.
@@ -36,7 +36,7 @@
           <span tal:condition="view/default_information_type"
             tal:attributes="class string:sprite ${private_class}"
             id="privacy-text">
-            New repositories for <tal:name replace="view/target/displayname"/> are
+            New repositories for <tal:name replace="view/target/display_name"/> are
             <strong tal:content="view/default_information_type_title" />.
           </span>
         </div>
@@ -78,7 +78,7 @@ git push --set-upstream origin master
           tal:define="count context/menu:branches/active_review_count|nothing;
                       link context/menu:branches/active_reviews|nothing"
           tal:condition="python: count &gt; 0">
-          <tal:project replace="context/displayname"/> has
+          <tal:project replace="context/display_name"/> has
           <tal:active-count replace="count"/>
           <tal:link replace="structure python: link.render().lower()"/>.
         </p>
diff --git a/lib/lp/registry/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml
index e277e94..2c12cc0 100644
--- a/lib/lp/registry/browser/configure.zcml
+++ b/lib/lp/registry/browser/configure.zcml
@@ -2562,6 +2562,10 @@
         path_expression="string:${oci_project/pillar/name}/+oci/${oci_project/name}"
         attribute_to_parent="person"
         />
+    <browser:navigation
+        module="lp.registry.browser.personociproject"
+        classes="PersonOCIProjectNavigation"
+        />
     <browser:url
         for="lp.registry.interfaces.personproduct.IPersonProduct"
         path_expression="product/name"
diff --git a/lib/lp/registry/browser/personociproject.py b/lib/lp/registry/browser/personociproject.py
new file mode 100644
index 0000000..aa991eb
--- /dev/null
+++ b/lib/lp/registry/browser/personociproject.py
@@ -0,0 +1,63 @@
+# Copyright 2019 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Views, menus, and traversal related to `PersonOCIProject`s."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+    'PersonOCIProjectNavigation',
+    ]
+
+from zope.component import queryAdapter
+from zope.interface import implementer
+from zope.traversing.interfaces import IPathAdapter
+
+from lp.code.browser.vcslisting import PersonTargetDefaultVCSNavigationMixin
+from lp.registry.interfaces.personociproject import IPersonOCIProject
+from lp.services.webapp import (
+    canonical_url,
+    Navigation,
+    StandardLaunchpadFacets,
+    )
+from lp.services.webapp.breadcrumb import Breadcrumb
+from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
+
+
+class PersonOCIProjectNavigation(
+        PersonTargetDefaultVCSNavigationMixin, Navigation):
+
+    usedfor = IPersonOCIProject
+
+
+# XXX cjwatson 2019-11-26: Do we need two breadcrumbs, one for the
+# distribution and one for the OCI project?
+@implementer(IMultiFacetedBreadcrumb)
+class PersonOCIProjectBreadcrumb(Breadcrumb):
+    """Breadcrumb for an `IPersonOCIProject`."""
+
+    @property
+    def text(self):
+        return self.context.oci_project.display_name
+
+    @property
+    def url(self):
+        if self._url is None:
+            return canonical_url(
+                self.context.oci_project, rootsite=self.rootsite)
+        else:
+            return self._url
+
+    @property
+    def icon(self):
+        return queryAdapter(
+            self.context.oci_project, IPathAdapter, name='image').icon()
+
+
+class PersonOCIProjectFacets(StandardLaunchpadFacets):
+    """The links that will appear in the facet menu for an `IPersonOCIProject`.
+    """
+
+    usedfor = IPersonOCIProject
+    enable_only = ['branches']