← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/misc-git-views into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/misc-git-views into lp:launchpad.

Commit message:
Introduce and link in {Person,Distribution}:+git.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/misc-git-views/+merge/261812

Introduce and link in {Person,Distribution}:+git. Neither of these can have a default, so the page will always start with "Other repositories", but it'll be reworked later.

Person:+code doesn't exist, as there's no way to determine the default.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/misc-git-views into lp:launchpad.
=== modified file 'lib/lp/code/browser/branchlisting.py'
--- lib/lp/code/browser/branchlisting.py	2015-06-04 23:38:44 +0000
+++ lib/lp/code/browser/branchlisting.py	2015-06-12 07:30:15 +0000
@@ -538,7 +538,14 @@
 
     # Many Bazaar branch listings have a closely related Git listing
     # that they should link to.
-    show_git_link = False
+    can_have_git_link = False
+
+    @property
+    def show_git_link(self):
+        if not self.can_have_git_link:
+            return False
+        c = IGitCollection(self.context)
+        return not c.visibleByUser(self.user).is_empty()
 
     # Set the feed types to be only the various branches feed links.  The
     # `feed_links` property will screen this list and produce only the feeds
@@ -938,6 +945,7 @@
     custom_widget('category', LaunchpadDropdownWidget)
 
     no_sort_by = (BranchListingSort.DEFAULT, BranchListingSort.OWNER)
+    can_have_git_link = True
 
     @property
     def no_branch_message(self):
@@ -990,11 +998,6 @@
         coll = super(PersonProductBranchesView, self)._getCollection()
         return coll.inProduct(self.context.product)
 
-    @property
-    def show_git_link(self):
-        c = IGitCollection(self.context)
-        return not c.visibleByUser(self.user).is_empty()
-
 
 class PersonTeamBranchesView(LaunchpadView):
     """View for team branches portlet."""
@@ -1234,6 +1237,7 @@
     """Initial view for products on the code virtual host."""
 
     show_set_development_focus = True
+    can_have_git_link = True
 
     @property
     def initial_values(self):
@@ -1305,11 +1309,6 @@
         """Is there a branch assigned as development focus?"""
         return self.development_focus_branch is not None
 
-    @property
-    def show_git_link(self):
-        c = IGitCollection(self.context)
-        return not c.visibleByUser(self.user).is_empty()
-
 
 class ProjectBranchesView(BranchListingView):
     """View for branch listing for a project."""
@@ -1363,6 +1362,8 @@
 class DistributionBranchListingView(BaseSourcePackageBranchesView):
     """A general listing of all branches in the distribution."""
 
+    can_have_git_link = True
+
     def _getCollection(self):
         return getUtility(IAllBranches).inDistribution(self.context)
 

=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml	2015-06-11 08:06:28 +0000
+++ lib/lp/code/browser/configure.zcml	2015-06-12 07:30:15 +0000
@@ -921,6 +921,18 @@
         name="+git"
         template="../templates/gitlisting.pt"/>
     <browser:page
+        for="lp.registry.interfaces.person.IPerson"
+        class="lp.code.browser.gitlisting.PlainGitListingView"
+        permission="zope.Public"
+        name="+git"
+        template="../templates/gitlisting.pt"/>
+    <browser:page
+        for="lp.registry.interfaces.distribution.IDistribution"
+        class="lp.code.browser.gitlisting.PlainGitListingView"
+        permission="zope.Public"
+        name="+git"
+        template="../templates/gitlisting.pt"/>
+    <browser:page
         for="lp.code.browser.gitlisting.IGitRepositoryBatchNavigator"
         name="+gitrepository-listing"
         template="../templates/gitrepository-listing.pt"
@@ -943,7 +955,7 @@
     <browser:defaultView
         for="lp.registry.interfaces.distribution.IDistribution"
         layer="lp.code.publisher.CodeLayer"
-        name="+branches"/>
+        name="+code"/>
 
     <browser:defaultView
         for="lp.registry.interfaces.distroseries.IDistroSeries"

=== modified file 'lib/lp/code/browser/gitlisting.py'
--- lib/lp/code/browser/gitlisting.py	2015-06-11 08:06:28 +0000
+++ lib/lp/code/browser/gitlisting.py	2015-06-12 07:30:15 +0000
@@ -161,3 +161,10 @@
 
     # PersonDistributionSourcePackage:+branches doesn't exist.
     show_bzr_link = False
+
+
+class PlainGitListingView(BaseGitListingView):
+
+    page_title = 'Git'
+    target = None
+    default_git_repository = None

=== modified file 'lib/lp/code/browser/tests/test_branchlisting.py'
--- lib/lp/code/browser/tests/test_branchlisting.py	2015-06-04 08:50:04 +0000
+++ lib/lp/code/browser/tests/test_branchlisting.py	2015-06-12 07:30:15 +0000
@@ -463,6 +463,20 @@
             list(view.series_links))
 
 
+class TestDistributionBranchesView(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_git_link(self):
+        dsp = self.factory.makeDistributionSourcePackage()
+        page = create_initialized_view(dsp.distribution, '+branches')()
+        self.assertNotIn('View Git repositories', page)
+
+        self.factory.makeGitRepository(target=dsp)
+        page = create_initialized_view(dsp.distribution, '+branches')()
+        self.assertIn('View Git repositories', page)
+
+
 class TestGroupedDistributionSourcePackageBranchesView(TestCaseWithFactory):
     """Test the groups for the branches of distribution source packages."""
 
@@ -709,6 +723,15 @@
         self.assertTrue(badname not in browser.contents)
         self.assertTrue(escapedname in browser.contents)
 
+    def test_git_link(self):
+        person = self.factory.makePerson()
+        page = create_initialized_view(person, '+branches')()
+        self.assertNotIn('View Git repositories', page)
+
+        self.factory.makeGitRepository(owner=person)
+        page = create_initialized_view(person, '+branches')()
+        self.assertIn('View Git repositories', page)
+
 
 class TestProjectGroupBranches(TestCaseWithFactory,
                                AjaxBatchNavigationMixin):

=== modified file 'lib/lp/code/browser/tests/test_gitlisting.py'
--- lib/lp/code/browser/tests/test_gitlisting.py	2015-06-11 08:06:28 +0000
+++ lib/lp/code/browser/tests/test_gitlisting.py	2015-06-12 07:30:15 +0000
@@ -9,7 +9,9 @@
 from zope.component import getUtility
 
 from lp.app.enums import InformationType
+from lp.app.interfaces.services import IService
 from lp.code.interfaces.gitrepository import IGitRepositorySet
+from lp.registry.enums import SharingPermission
 from lp.registry.model.persondistributionsourcepackage import (
     PersonDistributionSourcePackage,
     )
@@ -369,3 +371,102 @@
         self.factory.makeBranch(owner=self.owner, target=self.branch_target)
         view = create_initialized_view(self.owner_target, '+git')
         self.assertNotIn('View Bazaar branches', view())
+
+
+class TestPlainGitListingView:
+
+    layer = DatabaseFunctionalLayer
+
+    def test_rendering(self):
+        some_repo = self.factory.makeGitRepository(
+            owner=self.owner, target=self.target, name=u"foo")
+        self.factory.makeGitRefs(
+            some_repo,
+            paths=[u"refs/heads/master", u"refs/heads/bug-1234"])
+
+        other_repo = self.factory.makeGitRepository(
+            owner=self.owner, target=self.target, name=u"bar")
+        self.factory.makeGitRefs(other_repo, paths=[u"refs/heads/bug-2468"])
+
+        view = create_initialized_view(self.context, '+git')
+        self.assertIs(None, view.default_git_repository)
+
+        content = view()
+        soup = BeautifulSoup(content)
+
+        # No details about the default repo are shown, as a person
+        # without a target doesn't have a default repo
+        self.assertNotIn('Branches', content)
+        self.assertNotIn('Browse the code', content)
+        self.assertNotIn('git clone', content)
+        self.assertNotIn('bug-1234', content)
+
+        # All owned repos are listed.
+        table = soup.find(
+            'div', id='gitrepositories-table-listing').find('table')
+        self.assertContentEqual(
+            [some_repo.git_identity, other_repo.git_identity],
+            [link.find(text=True) for link in table.findAll('a')])
+
+    def test_copes_with_private_repos(self):
+        # XXX wgrant 2015-06-12: owner is self.user instead of
+        # self.owner here so the Distribution tests work.
+        # GitRepository._reconcileAccess doesn't handle distro repos
+        # properly, so an AccessPolicyGrant isn't sufficient.
+        invisible_repo = self.factory.makeGitRepository(
+            owner=self.user, target=self.target,
+            information_type=InformationType.PRIVATESECURITY)
+        other_repo = self.factory.makeGitRepository(
+            owner=self.owner, target=self.target,
+            information_type=InformationType.PUBLIC)
+
+        # An anonymous user can't see the private branch.
+        with anonymous_logged_in():
+            anon_view = create_initialized_view(self.context, '+git')
+            self.assertContentEqual(
+                [other_repo], anon_view.repo_collection.getRepositories())
+
+        # Neither can a random unprivileged user.
+        with person_logged_in(self.factory.makePerson()):
+            anon_view = create_initialized_view(self.context, '+git')
+            self.assertContentEqual(
+                [other_repo], anon_view.repo_collection.getRepositories())
+
+        # But someone who can see the repo gets the full view.
+        with person_logged_in(self.user):
+            owner_view = create_initialized_view(
+                self.context, '+git', user=self.user)
+            self.assertContentEqual(
+                [invisible_repo, other_repo],
+                owner_view.repo_collection.getRepositories())
+
+    def test_bzr_link(self):
+        # With a fresh product there's no Bazaar link.
+        view = create_initialized_view(self.context, '+git')
+        self.assertNotIn('View Bazaar branches', view())
+
+        # But it appears once we create a branch.
+        self.factory.makeBranch(owner=self.owner, target=self.branch_target)
+        view = create_initialized_view(self.context, '+git')
+        self.assertIn('View Bazaar branches', view())
+
+
+class TestPersonGitListingView(TestPlainGitListingView, TestCaseWithFactory):
+
+    def setUp(self):
+        super(TestPersonGitListingView, self).setUp()
+        self.context = self.user = self.owner = self.factory.makePerson()
+        self.target = self.branch_target = None
+
+
+class TestDistributionGitListingView(TestPlainGitListingView,
+                                     TestCaseWithFactory):
+
+    def setUp(self):
+        super(TestDistributionGitListingView, self).setUp()
+        self.target = self.factory.makeDistributionSourcePackage()
+        self.factory.makeDistroSeries(distribution=self.target.distribution)
+        self.branch_target = self.target.development_version
+        self.context = self.target.distribution
+        self.user = self.target.distribution.owner
+        self.owner = None

=== modified file 'lib/lp/code/browser/tests/test_vcslisting.py'
--- lib/lp/code/browser/tests/test_vcslisting.py	2015-06-11 09:21:02 +0000
+++ lib/lp/code/browser/tests/test_vcslisting.py	2015-06-12 07:30:15 +0000
@@ -8,12 +8,14 @@
 from zope.publisher.interfaces import NotFound
 
 from lp.code.browser.branchlisting import (
+    DistributionBranchListingView,
     GroupedDistributionSourcePackageBranchesView,
     PersonProductBranchesView,
     ProductBranchesView,
     )
 from lp.code.browser.gitlisting import (
     PersonTargetGitListingView,
+    PlainGitListingView,
     TargetGitListingView,
     )
 from lp.registry.enums import VCSType
@@ -120,3 +122,24 @@
 
     def test_git(self):
         self.assertCodeViewClass(VCSType.GIT, PersonTargetGitListingView)
+
+
+class TestDistributionDefaultVCSView(TestCaseWithFactory):
+    """Tests that Distribution:+code delegates to +git or +branches."""
+
+    layer = DatabaseFunctionalLayer
+
+    def assertCodeViewClass(self, vcs, cls):
+        distro = self.factory.makeDistribution(vcs=vcs)
+        self.assertEqual(vcs, distro.vcs)
+        view = test_traverse('/%s/+code' % distro.name)[1]
+        self.assertIsInstance(view, cls)
+
+    def test_default_unset(self):
+        self.assertCodeViewClass(None, DistributionBranchListingView)
+
+    def test_default_bzr(self):
+        self.assertCodeViewClass(VCSType.BZR, DistributionBranchListingView)
+
+    def test_git(self):
+        self.assertCodeViewClass(VCSType.GIT, PlainGitListingView)

=== modified file 'lib/lp/code/templates/generic-branch-listing.pt'
--- lib/lp/code/templates/generic-branch-listing.pt	2009-09-17 00:27:40 +0000
+++ lib/lp/code/templates/generic-branch-listing.pt	2015-06-12 07:30:15 +0000
@@ -10,6 +10,10 @@
 
   <div metal:fill-slot="main">
 
+    <span class="see-all" tal:condition="view/show_git_link">
+      <a tal:attributes="href context/fmt:url:code/+git">View Git repositories</a>
+    </span>
+
     <tal:no-branches condition="not: view/branch_count">
       <div id="branch-summary">
         There are no branches for <tal:name replace="context/displayname"/>

=== modified file 'lib/lp/code/templates/gitlisting.pt'
--- lib/lp/code/templates/gitlisting.pt	2015-06-11 08:06:28 +0000
+++ lib/lp/code/templates/gitlisting.pt	2015-06-12 07:30:15 +0000
@@ -8,36 +8,40 @@
 >
 <head>
   <tal:head-epilogue metal:fill-slot="head_epilogue">
-    <meta tal:condition="view/target/pillar/codehosting_usage/enumvalue:UNKNOWN"
-      name="robots" content="noindex,nofollow" />
+    <tal:has-target condition="view/target">
+      <meta tal:condition="view/target/pillar/codehosting_usage/enumvalue:UNKNOWN"
+        name="robots" content="noindex,nofollow" />
+    </tal:has-target>
   </tal:head-epilogue>
 </head>
 
 <body>
   <metal:side fill-slot="side" tal:define="context_menu context/menu:context">
-    <div id="branch-portlet"
-         tal:condition="not: view/target/pillar/codehosting_usage/enumvalue:UNKNOWN">
-      <div id="privacy"
-           tal:define="private_class python: 'private' if view.default_information_type_is_private else 'public'"
-           tal:attributes="class string:first portlet ${private_class}">
-        <span tal:condition="not: view/default_information_type"
-           id="privacy-text">
-          You can't create new repositories for
-          <tal:name replace="context/displayname"/>.
-          <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>.
-          </tal:sharing-link>
-        </span>
+    <tal:has-target condition="view/target">
+      <div id="branch-portlet"
+          tal:condition="not: view/target/pillar/codehosting_usage/enumvalue:UNKNOWN">
+        <div id="privacy"
+            tal:define="private_class python: 'private' if view.default_information_type_is_private else 'public'"
+            tal:attributes="class string:first portlet ${private_class}">
+          <span tal:condition="not: view/default_information_type"
+            id="privacy-text">
+            You can't create new repositories for
+            <tal:name replace="context/displayname"/>.
+            <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>.
+            </tal:sharing-link>
+          </span>
 
-        <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
-          <strong tal:content="view/default_information_type_title" />.
-        </span>
+          <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
+            <strong tal:content="view/default_information_type_title" />.
+          </span>
+        </div>
       </div>
-    </div>
+    </tal:has-target>
   </metal:side>
   <metal:main fill-slot="main">
     <span class="see-all" tal:condition="view/show_bzr_link">

=== modified file 'lib/lp/registry/browser/distribution.py'
--- lib/lp/registry/browser/distribution.py	2015-05-15 12:40:45 +0000
+++ lib/lp/registry/browser/distribution.py	2015-06-12 07:30:15 +0000
@@ -78,6 +78,7 @@
     StructuralSubscriptionTargetTraversalMixin,
     )
 from lp.buildmaster.interfaces.processor import IProcessorSet
+from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
 from lp.registry.browser import (
     add_subscribe_link,
     RegistryEditFormView,
@@ -138,7 +139,7 @@
 class DistributionNavigation(
     GetitemNavigation, BugTargetTraversalMixin, QuestionTargetTraversalMixin,
     FAQTargetNavigationMixin, StructuralSubscriptionTargetTraversalMixin,
-    PillarNavigationMixin):
+    PillarNavigationMixin, TargetDefaultVCSNavigationMixin):
 
     usedfor = IDistribution
 


Follow ups