← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/gitlisting into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/gitlisting into lp:launchpad.

Commit message:
Add Product:+git and PersonProduct:+git, and remove the temporary Git bits from Product:+branches.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/gitlisting/+merge/261066

Add Product:+git and PersonProduct:+git, displaying the details of the default repo and a list of any others. Product:+branches' temporary default Git rendering is gone.

The next branch includes link between the VCS pages if there are artifacts for the other one. A later branch will introduce a +code view which shows the default VCS at https://code.launchpad.net/PROJECT.

I'll also be reworking GitRepository:+index and experimenting with how best to integrate all of the the default repo's +index bits into the target's +git, but it's good enough for now.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/gitlisting into lp:launchpad.
=== modified file 'lib/lp/code/browser/branchlisting.py'
--- lib/lp/code/browser/branchlisting.py	2015-06-02 10:53:52 +0000
+++ lib/lp/code/browser/branchlisting.py	2015-06-04 09:15:20 +0000
@@ -68,7 +68,6 @@
 from lp.bugs.interfaces.bugbranch import IBugBranchSet
 from lp.code.browser.branch import BranchMirrorMixin
 from lp.code.browser.branchmergeproposallisting import ActiveReviewsView
-from lp.code.browser.gitrepository import GitRefBatchNavigator
 from lp.code.browser.summary import BranchCountSummaryView
 from lp.code.enums import (
     BranchLifecycleStatus,
@@ -85,7 +84,6 @@
 from lp.code.interfaces.branchcollection import IAllBranches
 from lp.code.interfaces.branchnamespace import IBranchNamespacePolicy
 from lp.code.interfaces.branchtarget import IBranchTarget
-from lp.code.interfaces.gitrepository import IGitRepositorySet
 from lp.code.interfaces.revision import IRevisionSet
 from lp.code.interfaces.revisioncache import IRevisionCache
 from lp.code.interfaces.seriessourcepackagebranch import (
@@ -526,7 +524,6 @@
     field_names = ['lifecycle', 'sort_by']
     development_focus_branch = None
     show_set_development_focus = False
-    default_git_repository = None
     custom_widget('lifecycle', LaunchpadDropdownWidget)
     custom_widget('sort_by', LaunchpadDropdownWidget)
     # Showing the series links is only really useful on product listing
@@ -1098,26 +1095,6 @@
         else:
             return None
 
-    @cachedproperty
-    def default_git_repository(self):
-        repository = getUtility(IGitRepositorySet).getDefaultRepository(
-            self.context)
-        if repository is None:
-            return None
-        elif check_permission('launchpad.View', repository):
-            return repository
-        else:
-            return None
-
-    def default_git_repository_branches(self):
-        """All branches in the default Git repository, sorted for display."""
-        return GitRefBatchNavigator(self, self.default_git_repository)
-
-    @property
-    def has_default_git_repository(self):
-        """Is there a default Git repository?"""
-        return self.default_git_repository is not None
-
     @property
     def no_branch_message(self):
         if (self.selected_lifecycle_status is not None

=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml	2015-06-02 08:57:02 +0000
+++ lib/lp/code/browser/configure.zcml	2015-06-04 09:15:20 +0000
@@ -875,6 +875,24 @@
         attribute_to_parent="repository"
         rootsite="code"/>
 
+    <browser:page
+        for="lp.registry.interfaces.product.IProduct"
+        class="lp.code.browser.gitlisting.TargetGitListingView"
+        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"
+        name="+git"
+        template="../templates/gitlisting.pt"/>
+    <browser:page
+        for="lp.code.browser.gitlisting.IGitRepositoryBatchNavigator"
+        name="+gitrepository-listing"
+        template="../templates/gitrepository-listing.pt"
+        permission="zope.Public"/>
+
     <browser:menus
         classes="ProductBranchesMenu"
         module="lp.code.browser.branchlisting"/>

=== added file 'lib/lp/code/browser/gitlisting.py'
--- lib/lp/code/browser/gitlisting.py	1970-01-01 00:00:00 +0000
+++ lib/lp/code/browser/gitlisting.py	2015-06-04 09:15:20 +0000
@@ -0,0 +1,154 @@
+# Copyright 2015 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""View classes for Git repository listings."""
+
+__metaclass__ = type
+
+__all__ = [
+    'PersonTargetGitListingView',
+    'TargetGitListingView',
+    ]
+
+from storm.expr import Desc
+from zope.component import getUtility
+from zope.interface import (
+    implements,
+    Interface,
+    )
+
+from lp.app.enums import PRIVATE_INFORMATION_TYPES
+from lp.code.browser.gitrepository import GitRefBatchNavigator
+from lp.code.interfaces.gitcollection import IGitCollection
+from lp.code.interfaces.gitnamespace import (
+    get_git_namespace,
+    IGitNamespacePolicy,
+    )
+from lp.code.interfaces.gitrepository import IGitRepositorySet
+from lp.code.model.gitrepository import GitRepository
+from lp.registry.interfaces.personproduct import IPersonProduct
+from lp.services.config import config
+from lp.services.propertycache import cachedproperty
+from lp.services.webapp.batching import TableBatchNavigator
+from lp.services.webapp.publisher import LaunchpadView
+from lp.services.webapp.authorization import check_permission
+
+
+class IGitRepositoryBatchNavigator(Interface):
+    pass
+
+
+class GitRepositoryBatchNavigator(TableBatchNavigator):
+    """Batch up Git repository listings."""
+    implements(IGitRepositoryBatchNavigator)
+
+    variable_name_prefix = 'repo'
+
+    def __init__(self, view, repo_collection):
+        super(GitRepositoryBatchNavigator, self).__init__(
+            repo_collection.getRepositories().order_by(
+                Desc(GitRepository.date_last_modified)),
+            view.request, size=config.launchpad.branchlisting_batch_size)
+        self.view = view
+        self.column_count = 2
+
+
+class BaseGitListingView(LaunchpadView):
+
+    @property
+    def target(self):
+        raise NotImplementedError()
+
+    @cachedproperty
+    def default_git_repository(self):
+        raise NotImplementedError()
+
+    @property
+    def repo_collection(self):
+        raise NotImplementedError()
+
+    def default_git_repository_branches(self):
+        """All branches in the default Git repository, sorted for display."""
+        return GitRefBatchNavigator(self, self.default_git_repository)
+
+    @cachedproperty
+    def default_information_type(self):
+        """The default information type for new repos."""
+        if self.user is None:
+            return None
+        namespace = get_git_namespace(self.target, self.user)
+        policy = IGitNamespacePolicy(namespace)
+        return policy.getDefaultInformationType(self.user)
+
+    @property
+    def default_information_type_title(self):
+        """The title of the default information type for new branches."""
+        information_type = self.default_information_type
+        if information_type is None:
+            return None
+        return information_type.title
+
+    @property
+    def default_information_type_is_private(self):
+        """The title of the default information type for new branches."""
+        return self.default_information_type in PRIVATE_INFORMATION_TYPES
+
+    @property
+    def repos(self):
+        return GitRepositoryBatchNavigator(self, self.repo_collection)
+
+
+class TargetGitListingView(BaseGitListingView):
+
+    page_title = 'Git'
+
+    @property
+    def target(self):
+        return self.context
+
+    @cachedproperty
+    def default_git_repository(self):
+        repo = getUtility(IGitRepositorySet).getDefaultRepository(
+            self.context)
+        if repo is None:
+            return None
+        elif check_permission('launchpad.View', repo):
+            return repo
+        else:
+            return None
+
+    @property
+    def repo_collection(self):
+        return IGitCollection(self.target).visibleByUser(self.user)
+
+
+class PersonTargetGitListingView(BaseGitListingView):
+
+    page_title = 'Git'
+
+    @property
+    def label(self):
+        return 'Git repositories for %s' % self.target.displayname
+
+    @property
+    def target(self):
+        if IPersonProduct.providedBy(self.context):
+            return self.context.product
+        else:
+            raise Exception("Unknown context: %r" % self.context)
+
+    @cachedproperty
+    def default_git_repository(self):
+        repo = getUtility(IGitRepositorySet).getDefaultRepositoryForOwner(
+            self.context.person, self.target)
+        if repo is None:
+            return None
+        elif check_permission('launchpad.View', repo):
+            return repo
+        else:
+            return None
+
+    @property
+    def repo_collection(self):
+        return IGitCollection(self.target).ownedBy(
+            self.context.person).visibleByUser(self.user)

=== added file 'lib/lp/code/browser/tests/test_gitlisting.py'
--- lib/lp/code/browser/tests/test_gitlisting.py	1970-01-01 00:00:00 +0000
+++ lib/lp/code/browser/tests/test_gitlisting.py	2015-06-04 09:15:20 +0000
@@ -0,0 +1,271 @@
+# Copyright 2015 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Unit tests for Git listing views."""
+
+__metaclass__ = type
+
+from BeautifulSoup import BeautifulSoup
+from zope.component import getUtility
+
+from lp.app.enums import InformationType
+from lp.code.interfaces.gitrepository import IGitRepositorySet
+from lp.registry.model.personproduct import PersonProduct
+from lp.testing import (
+    admin_logged_in,
+    anonymous_logged_in,
+    person_logged_in,
+    TestCaseWithFactory,
+    )
+from lp.testing.layers import DatabaseFunctionalLayer
+from lp.testing.views import create_initialized_view
+
+
+class TestTargetGitListingView(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_rendering(self):
+        owner = self.factory.makePerson(name=u"foowner")
+        product = self.factory.makeProduct(name=u"foo", owner=owner)
+        main_repo = self.factory.makeGitRepository(
+            owner=owner, target=product, name=u"foo")
+        self.factory.makeGitRefs(
+            main_repo,
+            paths=[u"refs/heads/master", u"refs/heads/1.0", u"refs/tags/1.1"])
+
+        other_repo = self.factory.makeGitRepository(
+            owner=self.factory.makePerson(name=u"contributor"),
+            target=product, name=u"foo")
+        self.factory.makeGitRefs(other_repo, paths=[u"refs/heads/bug-1234"])
+        self.factory.makeGitRepository(
+            owner=self.factory.makePerson(name=u"random"),
+            target=product, name=u"bar")
+
+        with admin_logged_in():
+            getUtility(IGitRepositorySet).setDefaultRepository(
+                target=product, repository=main_repo)
+            getUtility(IGitRepositorySet).setDefaultRepositoryForOwner(
+                owner=other_repo.owner, target=product, repository=other_repo,
+                user=other_repo.owner)
+
+        view = create_initialized_view(product, '+git')
+        self.assertEqual(main_repo, view.default_git_repository)
+
+        content = view()
+        soup = BeautifulSoup(content)
+
+        # Clone instructions for the default repo are present.
+        self.assertEqual(
+            'git://git.launchpad.dev/foo',
+            soup.find(attrs={'class': 'anon-url'}).find(text=True))
+        self.assertEqual(
+            'https://git.launchpad.dev/~foowner/foo/+git/foo',
+            soup.find(text='Browse the code').parent['href'])
+
+        # The default repo's branches are shown, but not its tags.
+        table = soup.find(
+            'div', id='default-repository-branches').find('table')
+        self.assertContentEqual(
+            ['1.0', 'master'],
+            [link.find(text=True) for link in table.findAll('a')])
+        self.assertEndsWith(
+            table.find(text="1.0").parent['href'],
+            u"/~foowner/foo/+git/foo/+ref/1.0")
+
+        # Other repos are listed.
+        table = soup.find(
+            'div', id='gitrepositories-table-listing').find('table')
+        self.assertContentEqual(
+            ['lp:foo', 'lp:~random/foo/+git/bar', 'lp:~contributor/foo'],
+            [link.find(text=True) for link in table.findAll('a')])
+        self.assertEndsWith(
+            table.find(text="lp:~contributor/foo").parent['href'],
+            u"/~contributor/foo/+git/foo")
+
+        # But not their branches.
+        self.assertNotIn('bug-1234', content)
+
+    def test_copes_with_no_default(self):
+        owner = self.factory.makePerson(name=u"foowner")
+        product = self.factory.makeProduct(name=u"foo", owner=owner)
+
+        self.factory.makeGitRepository(
+            owner=self.factory.makePerson(name=u"contributor"),
+            target=product, name=u"foo")
+
+        view = create_initialized_view(product, '+git')
+        self.assertIs(None, view.default_git_repository)
+
+        content = view()
+        soup = BeautifulSoup(content)
+
+        # No details about the non-existent default repo are shown.
+        # XXX: This should show instructions to create one.
+        self.assertNotIn('Branches', content)
+        self.assertNotIn('Browse the code', content)
+        self.assertNotIn('git clone', content)
+
+        # Other repos are listed.
+        table = soup.find(
+            'div', id='gitrepositories-table-listing').find('table')
+        self.assertContentEqual(
+            ['lp:~contributor/foo/+git/foo'],
+            [link.find(text=True) for link in table.findAll('a')])
+
+    def test_copes_with_private_repos(self):
+        product = self.factory.makeProduct()
+        invisible_repo = self.factory.makeGitRepository(
+            target=product, information_type=InformationType.PRIVATESECURITY)
+        other_repo = self.factory.makeGitRepository(
+            target=product, information_type=InformationType.PUBLIC)
+        with admin_logged_in():
+            getUtility(IGitRepositorySet).setDefaultRepository(
+                target=product, repository=invisible_repo)
+
+        # An anonymous user can't see the default.
+        with anonymous_logged_in():
+            anon_view = create_initialized_view(product, '+git')
+            self.assertIs(None, anon_view.default_git_repository)
+            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(product, '+git')
+            self.assertIs(None, anon_view.default_git_repository)
+            self.assertContentEqual(
+                [other_repo], anon_view.repo_collection.getRepositories())
+
+        # But someone who can see the repo gets the normal view.
+        with person_logged_in(product.owner):
+            owner_view = create_initialized_view(
+                product, '+git', user=product.owner)
+            self.assertEqual(invisible_repo, owner_view.default_git_repository)
+            self.assertContentEqual(
+                [invisible_repo, other_repo],
+                owner_view.repo_collection.getRepositories())
+
+
+class TestPersonTargetGitListingView(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_rendering(self):
+        owner = self.factory.makePerson(name=u"dev")
+        product = self.factory.makeProduct(name=u"foo")
+        default_repo = self.factory.makeGitRepository(
+            owner=owner, target=product, name=u"foo")
+        self.factory.makeGitRefs(
+            default_repo,
+            paths=[u"refs/heads/master", u"refs/heads/bug-1234"])
+
+        other_repo = self.factory.makeGitRepository(
+            owner=owner, target=product, name=u"bar")
+        self.factory.makeGitRefs(other_repo, paths=[u"refs/heads/bug-2468"])
+
+        with admin_logged_in():
+            getUtility(IGitRepositorySet).setDefaultRepositoryForOwner(
+                owner=owner, target=product, repository=default_repo,
+                user=owner)
+
+        view = create_initialized_view(PersonProduct(owner, product), '+git')
+        self.assertEqual(default_repo, view.default_git_repository)
+
+        content = view()
+        soup = BeautifulSoup(content)
+
+        # Clone instructions for the default repo are present.
+        self.assertEqual(
+            'git://git.launchpad.dev/~dev/foo',
+            soup.find(attrs={'class': 'anon-url'}).find(text=True))
+        self.assertEqual(
+            'https://git.launchpad.dev/~dev/foo/+git/foo',
+            soup.find(text='Browse the code').parent['href'])
+
+        # The default repo's branches are shown.
+        table = soup.find(
+            'div', id='default-repository-branches').find('table')
+        self.assertContentEqual(
+            ['master', 'bug-1234'],
+            [link.find(text=True) for link in table.findAll('a')])
+        self.assertEndsWith(
+            table.find(text="bug-1234").parent['href'],
+            u"/~dev/foo/+git/foo/+ref/bug-1234")
+
+        # Other repos are listed.
+        table = soup.find(
+            'div', id='gitrepositories-table-listing').find('table')
+        self.assertContentEqual(
+            ['lp:~dev/foo', 'lp:~dev/foo/+git/bar'],
+            [link.find(text=True) for link in table.findAll('a')])
+        self.assertEndsWith(
+            table.find(text="lp:~dev/foo/+git/bar").parent['href'],
+            u"/~dev/foo/+git/bar")
+
+        # But not their branches.
+        self.assertNotIn('bug-2468', content)
+
+    def test_copes_with_no_default(self):
+        owner = self.factory.makePerson(name=u"dev")
+        product = self.factory.makeProduct(name=u"foo", owner=owner)
+
+        self.factory.makeGitRepository(
+            owner=owner, target=product, name=u"foo")
+
+        view = create_initialized_view(PersonProduct(owner, product), '+git')
+        self.assertIs(None, view.default_git_repository)
+
+        content = view()
+        soup = BeautifulSoup(content)
+
+        # No details about the non-existent default repo are shown.
+        # XXX: This should show instructions to create one.
+        self.assertNotIn('Branches', content)
+        self.assertNotIn('Browse the code', content)
+        self.assertNotIn('git clone', content)
+
+        # Other repos are listed.
+        table = soup.find(
+            'div', id='gitrepositories-table-listing').find('table')
+        self.assertContentEqual(
+            ['lp:~dev/foo/+git/foo'],
+            [link.find(text=True) for link in table.findAll('a')])
+
+    def test_copes_with_private_repos(self):
+        owner = self.factory.makePerson(name=u"dev")
+        product = self.factory.makeProduct()
+        invisible_repo = self.factory.makeGitRepository(
+            owner=owner, target=product,
+            information_type=InformationType.PRIVATESECURITY)
+        other_repo = self.factory.makeGitRepository(
+            owner=owner, target=product,
+            information_type=InformationType.PUBLIC)
+        with admin_logged_in():
+            getUtility(IGitRepositorySet).setDefaultRepositoryForOwner(
+                owner=owner, target=product, repository=invisible_repo,
+                user=owner)
+
+        pp = PersonProduct(owner, product)
+
+        # An anonymous user can't see the default.
+        with anonymous_logged_in():
+            anon_view = create_initialized_view(pp, '+git')
+            self.assertIs(None, anon_view.default_git_repository)
+            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(pp, '+git')
+            self.assertIs(None, anon_view.default_git_repository)
+            self.assertContentEqual(
+                [other_repo], anon_view.repo_collection.getRepositories())
+
+        # But someone who can see the repo gets the normal view.
+        with person_logged_in(owner):
+            owner_view = create_initialized_view(pp, '+git', user=owner)
+            self.assertEqual(invisible_repo, owner_view.default_git_repository)
+            self.assertContentEqual(
+                [invisible_repo, other_repo],
+                owner_view.repo_collection.getRepositories())

=== modified file 'lib/lp/code/browser/tests/test_product.py'
--- lib/lp/code/browser/tests/test_product.py	2015-06-02 06:35:38 +0000
+++ lib/lp/code/browser/tests/test_product.py	2015-06-04 09:15:20 +0000
@@ -19,7 +19,6 @@
     ServiceUsage,
     )
 from lp.code.enums import BranchType
-from lp.code.interfaces.gitrepository import IGitRepositorySet
 from lp.code.interfaces.revision import IRevisionSet
 from lp.registry.enums import BranchSharingPolicy
 from lp.services.webapp import canonical_url
@@ -146,38 +145,6 @@
         expected = 'There are no branches for %s' % product.displayname
         self.assertIn(expected, html)
 
-    def test_no_default_git_repository(self):
-        # If there is no default Git repository, Product:+branches does not
-        # try to render one.
-        product = self.factory.makeProduct()
-        view = create_initialized_view(
-            product, '+branches', rootsite='code', principal=product.owner)
-        self.assertIsNone(view.default_git_repository)
-        self.assertFalse(view.has_default_git_repository)
-        content = view()
-        self.assertNotIn('git clone', content)
-
-    def test_default_git_repository(self):
-        # If there is a default Git repository, Product:+branches shows a
-        # summary of its branches.
-        product = self.factory.makeProduct()
-        repository = self.factory.makeGitRepository(target=product)
-        self.factory.makeGitRefs(
-            repository=repository,
-            paths=[u"refs/heads/master", u"refs/heads/another-branch"])
-        with person_logged_in(product.owner):
-            getUtility(IGitRepositorySet).setDefaultRepository(
-                product, repository)
-        view = create_initialized_view(
-            product, '+branches', rootsite='code', principal=product.owner)
-        self.assertEqual(repository, view.default_git_repository)
-        self.assertTrue(view.has_default_git_repository)
-        content = view()
-        self.assertIn('git clone', content)
-        # XXX cjwatson 2015-04-30: These tests are not very precise.
-        self.assertIn('master', content)
-        self.assertIn('another-branch', content)
-
 
 class TestProductBranchesServiceUsages(ProductTestBase, BrowserTestCase):
     """Tests for the product code page, especially the usage messasges."""

=== added file 'lib/lp/code/templates/gitlisting.pt'
--- lib/lp/code/templates/gitlisting.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/code/templates/gitlisting.pt	2015-06-04 09:15:20 +0000
@@ -0,0 +1,72 @@
+<html
+  xmlns="http://www.w3.org/1999/xhtml";
+  xmlns:tal="http://xml.zope.org/namespaces/tal";
+  xmlns:metal="http://xml.zope.org/namespaces/metal";
+  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+  metal:use-macro="view/macro:page/main_side"
+  i18n:domain="launchpad"
+>
+<head>
+  <tal:head-epilogue metal:fill-slot="head_epilogue">
+    <meta tal:condition="view/target/codehosting_usage/enumvalue:UNKNOWN"
+      name="robots" content="noindex,nofollow" />
+  </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/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>
+      </div>
+    </div>
+  </metal:side>
+  <metal:main fill-slot="main">
+    <tal:default-repository
+        condition="view/default_git_repository"
+        define="repository view/default_git_repository">
+      <div class="yui-g first">
+        <div id="default-repository-management" class="top-portlet">
+          <tal:repository-management
+            replace="structure repository/@@++repository-management" />
+        </div>
+      </div>
+
+      <div class="yui-g">
+        <div id="default-repository-branches" class="portlet"
+            tal:define="branches view/default_git_repository_branches">
+          <h2>Branches</h2>
+          <tal:default-repository-branches
+            replace="structure branches/@@+ref-listing" />
+        </div>
+      </div>
+    </tal:default-repository>
+
+    <div class="yui-g">
+      <div id="other-repositories" class="portlet">
+        <h2>Other repositories</h2>
+        <tal:other-repos
+          content="structure view/repos/@@+gitrepository-listing" />
+      </div>
+    </div>
+  </metal:main>
+</body>
+</html>

=== added file 'lib/lp/code/templates/gitrepository-listing.pt'
--- lib/lp/code/templates/gitrepository-listing.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/code/templates/gitrepository-listing.pt	2015-06-04 09:15:20 +0000
@@ -0,0 +1,30 @@
+<div id="gitrepositories-table-listing"
+  xmlns:tal="http://xml.zope.org/namespaces/tal";
+  xmlns:metal="http://xml.zope.org/namespaces/metal";
+  >
+  <tal:needs-batch condition="context/has_multiple_pages">
+    <div id="branch-batch-links">
+      <tal:navigation replace="structure context/@@+navigation-links-upper" />
+    </div>
+  </tal:needs-batch>
+  <table class="listing">
+    <thead>
+      <tr>
+        <th>Name</th>
+        <th>Last Modified</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr tal:repeat="repo context/currentBatch">
+        <td>
+          <a tal:content="repo/display_name"
+              tal:attributes="href repo/fmt:url">lp:foo</a>
+        </td>
+        <td tal:content="repo/date_last_modified/fmt:approximatedate">
+          2 hours ago
+        </td>
+      </tr>
+    </tbody>
+  </table>
+  <tal:navigation replace="structure context/@@+navigation-links-lower" />
+</div>

=== modified file 'lib/lp/code/templates/product-branch-summary.pt'
--- lib/lp/code/templates/product-branch-summary.pt	2015-05-01 13:20:09 +0000
+++ lib/lp/code/templates/product-branch-summary.pt	2015-06-04 09:15:20 +0000
@@ -57,7 +57,7 @@
   </div>
 
   <tal:no-branches
-      condition="python: not view.branch_count and not view.has_default_git_repository">
+      condition="not: view/branch_count">
     There are no branches for <tal:project-name replace="context/displayname"/>
     in Launchpad.
     <tal:can-configure condition="view/can_configure_branches">
@@ -79,23 +79,6 @@
     </tal:can-configure>
   </tal:no-branches>
 
-  <div tal:condition="view/has_default_git_repository"
-       style="margin: 1em 0"
-       tal:define="repository view/default_git_repository">
-    You can
-    <a tal:attributes="href repository/getCodebrowseUrl">browse the
-      source code</a>
-    for the default Git repository or get a copy of the repository using
-    the command:<br/>
-    <tt class="command">git clone
-    <tal:logged-in condition="view/user">
-      <tal:git-ssh-url replace="repository/ssh_url"/>
-    </tal:logged-in>
-    <tal:not-logged-in condition="not: view/user">
-      <tal:git-anon-url replace="repository/anon_url"/>
-    </tal:not-logged-in></tt>
-  </div>
-
   <tal:has-branches condition="view/branch_count">
     <div tal:condition="view/has_development_focus_branch"
          style="margin: 1em 0"

=== modified file 'lib/lp/code/templates/product-branches.pt'
--- lib/lp/code/templates/product-branches.pt	2015-06-01 08:43:09 +0000
+++ lib/lp/code/templates/product-branches.pt	2015-06-04 09:15:20 +0000
@@ -60,16 +60,6 @@
       condition="not: view/context/codehosting_usage/enumvalue:UNKNOWN"
       replace="structure context/@@+portlet-product-branchstatistics" />
 
-    <tal:has-default-git-repository condition="view/has_default_git_repository">
-      <div id="default-repository-branches" class="portlet"
-           tal:define="repository view/default_git_repository;
-                       branches view/default_git_repository_branches">
-        <h2>Git branches</h2>
-        <tal:default-repository-branches
-          replace="structure branches/@@+ref-listing" />
-      </div>
-    </tal:has-default-git-repository>
-
     <tal:has-branches condition="view/branch_count"
                       define="branches view/branches">
       <div class="portlet">


Follow ups