← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
Add (Person)DistributionSourcePackage:+git and +code.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/dsp-git/+merge/261706

Add (Person)DistributionSourcePackage:+git and +code.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/dsp-git into lp:launchpad.
=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml	2015-06-10 10:19:25 +0000
+++ lib/lp/code/browser/configure.zcml	2015-06-11 10:29:28 +0000
@@ -903,12 +903,24 @@
         name="+git"
         template="../templates/gitlisting.pt"/>
     <browser:page
+        for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
+        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.registry.interfaces.persondistributionsourcepackage.IPersonDistributionSourcePackage"
+        class="lp.code.browser.gitlisting.PersonDistributionSourcePackageGitListingView"
+        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"

=== modified file 'lib/lp/code/browser/gitlisting.py'
--- lib/lp/code/browser/gitlisting.py	2015-06-04 23:38:44 +0000
+++ lib/lp/code/browser/gitlisting.py	2015-06-11 10:29:28 +0000
@@ -27,12 +27,15 @@
     )
 from lp.code.interfaces.gitrepository import IGitRepositorySet
 from lp.code.model.gitrepository import GitRepository
+from lp.registry.interfaces.persondistributionsourcepackage import (
+    IPersonDistributionSourcePackage,
+    )
 from lp.registry.interfaces.personproduct import IPersonProduct
 from lp.services.config import config
 from lp.services.propertycache import cachedproperty
+from lp.services.webapp.authorization import check_permission
 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):
@@ -136,6 +139,8 @@
     def target(self):
         if IPersonProduct.providedBy(self.context):
             return self.context.product
+        elif IPersonDistributionSourcePackage.providedBy(self.context):
+            return self.context.distro_source_package
         else:
             raise Exception("Unknown context: %r" % self.context)
 
@@ -149,3 +154,10 @@
             return repo
         else:
             return None
+
+
+class PersonDistributionSourcePackageGitListingView(
+        PersonTargetGitListingView):
+
+    # PersonDistributionSourcePackage:+branches doesn't exist.
+    show_bzr_link = False

=== modified file 'lib/lp/code/browser/tests/test_gitlisting.py'
--- lib/lp/code/browser/tests/test_gitlisting.py	2015-06-04 23:38:44 +0000
+++ lib/lp/code/browser/tests/test_gitlisting.py	2015-06-11 10:29:28 +0000
@@ -10,6 +10,9 @@
 
 from lp.app.enums import InformationType
 from lp.code.interfaces.gitrepository import IGitRepositorySet
+from lp.registry.model.persondistributionsourcepackage import (
+    PersonDistributionSourcePackage,
+    )
 from lp.registry.model.personproduct import PersonProduct
 from lp.testing import (
     admin_logged_in,
@@ -22,35 +25,33 @@
 from lp.testing.views import create_initialized_view
 
 
-class TestTargetGitListingView(TestCaseWithFactory):
+class TestTargetGitListingView:
 
     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")
+            owner=self.owner, target=self.target, 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")
+            target=self.target, 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")
+            target=self.target, name=u"bar")
 
         with admin_logged_in():
             getUtility(IGitRepositorySet).setDefaultRepository(
-                target=product, repository=main_repo)
+                target=self.target, repository=main_repo)
             getUtility(IGitRepositorySet).setDefaultRepositoryForOwner(
-                owner=other_repo.owner, target=product, repository=other_repo,
-                user=other_repo.owner)
+                owner=other_repo.owner, target=self.target,
+                repository=other_repo, user=other_repo.owner)
 
-        view = create_initialized_view(product, '+git')
+        view = create_initialized_view(self.target, '+git')
         self.assertEqual(main_repo, view.default_git_repository)
 
         content = view()
@@ -58,10 +59,11 @@
 
         # Clone instructions for the default repo are present.
         self.assertEqual(
-            'git://git.launchpad.dev/foo',
+            'git://git.launchpad.dev/%s' % self.target_path,
             soup.find(attrs={'class': 'anon-url'}).find(text=True))
         self.assertEqual(
-            'https://git.launchpad.dev/~foowner/foo/+git/foo',
+            'https://git.launchpad.dev/~foowner/%s/+git/foo'
+            % self.target_path,
             soup.find(text='Browse the code').parent['href'])
 
         # The default repo's branches are shown, but not its tags.
@@ -72,50 +74,49 @@
             [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")
+            u"/~foowner/%s/+git/foo/+ref/1.0" % self.target_path)
 
         # 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'],
+            ['lp:%s' % self.target_path,
+             'lp:~random/%s/+git/bar' % self.target_path,
+             'lp:~contributor/%s' % self.target_path],
             [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")
+            table.find(
+                text="lp:~contributor/%s" % self.target_path).parent['href'],
+            u"/~contributor/%s/+git/foo" % self.target_path)
 
         # But not their branches.
         self.assertNotIn('bug-1234', content)
 
     def test_query_count(self):
-        owner = self.factory.makePerson(name=u"foowner")
-        product = self.factory.makeProduct(name=u"foo", owner=owner)
-        main_repo = self.factory.makeGitRepository(target=product)
+        main_repo = self.factory.makeGitRepository(target=self.target)
         for i in range(10):
             self.factory.makeGitRefs(main_repo)
 
         for i in range(10):
-            other_repo = self.factory.makeGitRepository(target=product)
+            other_repo = self.factory.makeGitRepository(target=self.target)
             self.factory.makeGitRefs(other_repo)
 
         with admin_logged_in():
             getUtility(IGitRepositorySet).setDefaultRepository(
-                target=product, repository=main_repo)
+                target=self.target, repository=main_repo)
             getUtility(IGitRepositorySet).setDefaultRepositoryForOwner(
-                owner=other_repo.owner, target=product, repository=other_repo,
-                user=other_repo.owner)
+                owner=other_repo.owner, target=self.target,
+                repository=other_repo, user=other_repo.owner)
 
-        self.assertThat(product, BrowsesWithQueryLimit(32, owner, '+git'))
+        self.assertThat(
+            self.target, BrowsesWithQueryLimit(32, self.owner, '+git'))
 
     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")
+            target=self.target, name=u"foo")
 
-        view = create_initialized_view(product, '+git')
+        view = create_initialized_view(self.target, '+git')
         self.assertIs(None, view.default_git_repository)
 
         content = view()
@@ -131,78 +132,74 @@
         table = soup.find(
             'div', id='gitrepositories-table-listing').find('table')
         self.assertContentEqual(
-            ['lp:~contributor/foo/+git/foo'],
+            ['lp:~contributor/%s/+git/foo' % self.target_path],
             [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)
+            owner=self.owner, target=self.target,
+            information_type=InformationType.PRIVATESECURITY)
         other_repo = self.factory.makeGitRepository(
-            target=product, information_type=InformationType.PUBLIC)
+            target=self.target, information_type=InformationType.PUBLIC)
         with admin_logged_in():
             getUtility(IGitRepositorySet).setDefaultRepository(
-                target=product, repository=invisible_repo)
+                target=self.target, repository=invisible_repo)
 
         # An anonymous user can't see the default.
         with anonymous_logged_in():
-            anon_view = create_initialized_view(product, '+git')
+            anon_view = create_initialized_view(self.target, '+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')
+            anon_view = create_initialized_view(self.target, '+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):
+        with person_logged_in(self.owner):
             owner_view = create_initialized_view(
-                product, '+git', user=product.owner)
+                self.target, '+git', user=self.owner)
             self.assertEqual(invisible_repo, owner_view.default_git_repository)
             self.assertContentEqual(
                 [invisible_repo, other_repo],
                 owner_view.repo_collection.getRepositories())
 
     def test_bzr_link(self):
-        product = self.factory.makeProduct()
-
         # With a fresh product there's no Bazaar link.
-        view = create_initialized_view(product, '+git')
+        view = create_initialized_view(self.target, '+git')
         self.assertNotIn('View Bazaar branches', view())
 
         # But it appears once we create a branch.
-        self.factory.makeBranch(product=product)
-        view = create_initialized_view(product, '+git')
+        self.factory.makeBranch(target=self.branch_target)
+        view = create_initialized_view(self.target, '+git')
         self.assertIn('View Bazaar branches', view())
 
 
-class TestPersonTargetGitListingView(TestCaseWithFactory):
+class TestPersonTargetGitListingView:
 
     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")
+            owner=self.owner, target=self.target, 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")
+            owner=self.owner, target=self.target, 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)
+                owner=self.owner, target=self.target, repository=default_repo,
+                user=self.owner)
 
-        view = create_initialized_view(PersonProduct(owner, product), '+git')
+        view = create_initialized_view(self.owner_target, '+git')
         self.assertEqual(default_repo, view.default_git_repository)
 
         content = view()
@@ -210,10 +207,10 @@
 
         # Clone instructions for the default repo are present.
         self.assertEqual(
-            'git://git.launchpad.dev/~dev/foo',
+            'git://git.launchpad.dev/~dev/%s' % self.target_path,
             soup.find(attrs={'class': 'anon-url'}).find(text=True))
         self.assertEqual(
-            'https://git.launchpad.dev/~dev/foo/+git/foo',
+            'https://git.launchpad.dev/~dev/%s/+git/foo' % self.target_path,
             soup.find(text='Browse the code').parent['href'])
 
         # The default repo's branches are shown.
@@ -224,29 +221,28 @@
             [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")
+            u"/~dev/%s/+git/foo/+ref/bug-1234" % self.target_path)
 
         # Other repos are listed.
         table = soup.find(
             'div', id='gitrepositories-table-listing').find('table')
         self.assertContentEqual(
-            ['lp:~dev/foo', 'lp:~dev/foo/+git/bar'],
+            ['lp:~dev/%s' % self.target_path,
+             'lp:~dev/%s/+git/bar' % self.target_path],
             [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")
+            table.find(
+                text="lp:~dev/%s/+git/bar" % self.target_path).parent['href'],
+            u"/~dev/%s/+git/bar" % self.target_path)
 
         # 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")
+            owner=self.owner, target=self.target, name=u"foo")
 
-        view = create_initialized_view(PersonProduct(owner, product), '+git')
+        view = create_initialized_view(self.owner_target, '+git')
         self.assertIs(None, view.default_git_repository)
 
         content = view()
@@ -262,57 +258,114 @@
         table = soup.find(
             'div', id='gitrepositories-table-listing').find('table')
         self.assertContentEqual(
-            ['lp:~dev/foo/+git/foo'],
+            ['lp:~dev/%s/+git/foo' % self.target_path],
             [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,
+            owner=self.owner, target=self.target,
             information_type=InformationType.PRIVATESECURITY)
         other_repo = self.factory.makeGitRepository(
-            owner=owner, target=product,
+            owner=self.owner, target=self.target,
             information_type=InformationType.PUBLIC)
         with admin_logged_in():
             getUtility(IGitRepositorySet).setDefaultRepositoryForOwner(
-                owner=owner, target=product, repository=invisible_repo,
-                user=owner)
-
-        pp = PersonProduct(owner, product)
+                owner=self.owner, target=self.target,
+                repository=invisible_repo, user=self.owner)
 
         # An anonymous user can't see the default.
         with anonymous_logged_in():
-            anon_view = create_initialized_view(pp, '+git')
+            anon_view = create_initialized_view(self.owner_target, '+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')
+            anon_view = create_initialized_view(self.owner_target, '+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)
+        with person_logged_in(self.owner):
+            owner_view = create_initialized_view(
+                self.owner_target, '+git', user=self.owner)
             self.assertEqual(invisible_repo, owner_view.default_git_repository)
             self.assertContentEqual(
                 [invisible_repo, other_repo],
                 owner_view.repo_collection.getRepositories())
 
     def test_bzr_link(self):
-        owner = self.factory.makePerson()
-        product = self.factory.makeProduct()
-        pp = PersonProduct(owner, product)
-
         # With a fresh product there's no Bazaar link.
-        view = create_initialized_view(pp, '+git')
+        view = create_initialized_view(self.owner_target, '+git')
         self.assertNotIn('View Bazaar branches', view())
 
         # But it appears once we create a branch.
-        self.factory.makeBranch(owner=owner, product=product)
-        view = create_initialized_view(pp, '+git')
+        self.factory.makeBranch(owner=self.owner, target=self.branch_target)
+        view = create_initialized_view(self.owner_target, '+git')
         self.assertIn('View Bazaar branches', view())
+
+
+class TestProductGitListingView(TestTargetGitListingView,
+                                TestCaseWithFactory):
+
+    def setUp(self):
+        super(TestProductGitListingView, self).setUp()
+        self.owner = self.factory.makePerson(name=u"foowner")
+        self.target = self.factory.makeProduct(name=u"foo", owner=self.owner)
+        self.target_path = u"foo"
+        self.branch_target = self.target
+
+
+class TestPersonProductGitListingView(TestPersonTargetGitListingView,
+                                      TestCaseWithFactory):
+
+    def setUp(self):
+        super(TestPersonProductGitListingView, self).setUp()
+        self.owner = self.factory.makePerson(name=u"dev")
+        self.target = self.factory.makeProduct(name=u"foo")
+        self.target_path = u"foo"
+        self.owner_target = PersonProduct(self.owner, self.target)
+        self.branch_target = self.target
+
+
+class TestDistributionSourcePackageGitListingView(TestTargetGitListingView,
+                                                  TestCaseWithFactory):
+
+    def setUp(self):
+        super(TestDistributionSourcePackageGitListingView, self).setUp()
+        self.owner = self.factory.makePerson(name=u"foowner")
+        distro = self.factory.makeDistribution(name=u"foo", owner=self.owner)
+        self.target = self.factory.makeDistributionSourcePackage(
+            distribution=distro, sourcepackagename=u"bar")
+        self.target_path = u"foo/+source/bar"
+        self.factory.makeDistroSeries(distribution=distro)
+        self.branch_target = self.target.development_version
+
+
+class TestPersonDistributionSourcePackageGitListingView(
+        TestPersonTargetGitListingView, TestCaseWithFactory):
+
+    def setUp(self):
+        super(TestPersonDistributionSourcePackageGitListingView, self).setUp()
+        self.owner = self.factory.makePerson(name=u"dev")
+        distro = self.factory.makeDistribution(name=u"foo", owner=self.owner)
+        self.target = self.factory.makeDistributionSourcePackage(
+            distribution=distro, sourcepackagename=u"bar")
+        self.target_path = u"foo/+source/bar"
+        self.owner_target = PersonDistributionSourcePackage(
+            self.owner, self.target)
+        self.factory.makeDistroSeries(distribution=distro)
+        self.branch_target = self.target.development_version
+
+    def test_bzr_link(self):
+        # With a fresh target there's no Bazaar link.
+        view = create_initialized_view(self.owner_target, '+git')
+        self.assertNotIn('View Bazaar branches', view())
+
+        # As a special case for PersonDistributionSourcePackage, no link
+        # appears even if there is a branch. There's no PersonDSP:+branches.
+        self.factory.makeBranch(owner=self.owner, target=self.branch_target)
+        view = create_initialized_view(self.owner_target, '+git')
+        self.assertNotIn('View Bazaar branches', view())

=== modified file 'lib/lp/code/browser/tests/test_vcslisting.py'
--- lib/lp/code/browser/tests/test_vcslisting.py	2015-06-11 06:23:17 +0000
+++ lib/lp/code/browser/tests/test_vcslisting.py	2015-06-11 10:29:28 +0000
@@ -5,7 +5,10 @@
 
 __metaclass__ = type
 
+from zope.publisher.interfaces import NotFound
+
 from lp.code.browser.branchlisting import (
+    GroupedDistributionSourcePackageBranchesView,
     PersonProductBranchesView,
     ProductBranchesView,
     )
@@ -24,21 +27,20 @@
 
     layer = DatabaseFunctionalLayer
 
+    def assertCodeViewClass(self, vcs, cls):
+        product = self.factory.makeProduct(vcs=vcs)
+        self.assertEqual(vcs, product.vcs)
+        view = test_traverse('/%s/+code' % product.name)[1]
+        self.assertIsInstance(view, cls)
+
     def test_default_unset(self):
-        product = self.factory.makeProduct()
-        self.assertIs(None, product.vcs)
-        view = test_traverse('/%s/+code' % product.name)[1]
-        self.assertIsInstance(view, ProductBranchesView)
+        self.assertCodeViewClass(None, ProductBranchesView)
 
     def test_default_bzr(self):
-        product = self.factory.makeProduct(vcs=VCSType.BZR)
-        view = test_traverse('/%s/+code' % product.name)[1]
-        self.assertIsInstance(view, ProductBranchesView)
+        self.assertCodeViewClass(VCSType.BZR, ProductBranchesView)
 
     def test_git(self):
-        product = self.factory.makeProduct(vcs=VCSType.GIT)
-        view = test_traverse('/%s/+code' % product.name)[1]
-        self.assertIsInstance(view, TargetGitListingView)
+        self.assertCodeViewClass(VCSType.GIT, TargetGitListingView)
 
 
 class TestPersonProductDefaultVCSView(TestCaseWithFactory):
@@ -61,3 +63,60 @@
 
     def test_git(self):
         self.assertCodeViewClass(VCSType.GIT, PersonTargetGitListingView)
+
+
+class TestDistributionSourcePackageDefaultVCSView(TestCaseWithFactory):
+    """Tests that DSP:+code delegates to +git or +branches."""
+
+    layer = DatabaseFunctionalLayer
+
+    def assertCodeViewClass(self, vcs, cls):
+        distro = self.factory.makeDistribution(vcs=vcs)
+        dsp = self.factory.makeDistributionSourcePackage(distribution=distro)
+        self.assertEqual(vcs, distro.vcs)
+        view = test_traverse(
+            '/%s/+source/%s/+code'
+            % (distro.name, dsp.sourcepackagename.name))[1]
+        self.assertIsInstance(view, cls)
+
+    def test_default_unset(self):
+        self.assertCodeViewClass(
+            None, GroupedDistributionSourcePackageBranchesView)
+
+    def test_default_bzr(self):
+        self.assertCodeViewClass(
+            VCSType.BZR, GroupedDistributionSourcePackageBranchesView)
+
+    def test_git(self):
+        self.assertCodeViewClass(VCSType.GIT, TargetGitListingView)
+
+
+class TestPersonDistributionSourcePackageDefaultVCSView(TestCaseWithFactory):
+    """Tests that PersonDSP:+code delegates to +git or 404s.
+
+    It can't delegate to +branches, as PersonDSP:+branches doesn't exist.
+    """
+
+    layer = DatabaseFunctionalLayer
+
+    def assertCodeViewClass(self, vcs, cls):
+        person = self.factory.makePerson()
+        distro = self.factory.makeDistribution(vcs=vcs)
+        dsp = self.factory.makeDistributionSourcePackage(distribution=distro)
+        self.assertEqual(vcs, distro.vcs)
+        try:
+            view = test_traverse(
+                '~%s/%s/+source/%s/+code'
+                % (person.name, distro.name, dsp.sourcepackagename.name))[1]
+        except NotFound:
+            view = None
+        self.assertIsInstance(view, cls)
+
+    def test_default_unset(self):
+        self.assertCodeViewClass(None, type(None))
+
+    def test_default_bzr(self):
+        self.assertCodeViewClass(VCSType.BZR, type(None))
+
+    def test_git(self):
+        self.assertCodeViewClass(VCSType.GIT, PersonTargetGitListingView)

=== modified file 'lib/lp/code/browser/vcslisting.py'
--- lib/lp/code/browser/vcslisting.py	2015-06-10 10:19:25 +0000
+++ lib/lp/code/browser/vcslisting.py	2015-06-11 10:29:28 +0000
@@ -5,9 +5,13 @@
 
 __metaclass__ = type
 
-from zope.component import getMultiAdapter
+from zope.component import queryMultiAdapter
 
 from lp.registry.enums import VCSType
+from lp.registry.interfaces.persondistributionsourcepackage import (
+    IPersonDistributionSourcePackage,
+    )
+from lp.registry.interfaces.personproduct import IPersonProduct
 from lp.services.webapp import stepto
 
 
@@ -21,7 +25,7 @@
             view_name = '+git'
         else:
             raise AssertionError("Unknown VCS")
-        return getMultiAdapter(
+        return queryMultiAdapter(
             (self.context, self.request), name=view_name)
 
 
@@ -29,11 +33,17 @@
 
     @stepto("+code")
     def traverse_code_view(self):
-        if self.context.product.vcs in (VCSType.BZR, None):
+        if IPersonProduct.providedBy(self.context):
+            target = self.context.product
+        elif IPersonDistributionSourcePackage.providedBy(self.context):
+            target = self.context.distro_source_package
+        else:
+            raise AssertionError("Unknown target: %r" % self.context)
+        if target.pillar.vcs in (VCSType.BZR, None):
             view_name = '+branches'
-        elif self.context.product.vcs == VCSType.GIT:
+        elif target.pillar.vcs == VCSType.GIT:
             view_name = '+git'
         else:
             raise AssertionError("Unknown VCS")
-        return getMultiAdapter(
+        return queryMultiAdapter(
             (self.context, self.request), name=view_name)

=== modified file 'lib/lp/code/templates/gitlisting.pt'
--- lib/lp/code/templates/gitlisting.pt	2015-06-04 07:53:19 +0000
+++ lib/lp/code/templates/gitlisting.pt	2015-06-11 10:29:28 +0000
@@ -8,7 +8,7 @@
 >
 <head>
   <tal:head-epilogue metal:fill-slot="head_epilogue">
-    <meta tal:condition="view/target/codehosting_usage/enumvalue:UNKNOWN"
+    <meta tal:condition="view/target/pillar/codehosting_usage/enumvalue:UNKNOWN"
       name="robots" content="noindex,nofollow" />
   </tal:head-epilogue>
 </head>
@@ -16,7 +16,7 @@
 <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">
+         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}">

=== modified file 'lib/lp/registry/browser/distributionsourcepackage.py'
--- lib/lp/registry/browser/distributionsourcepackage.py	2014-11-27 20:52:37 +0000
+++ lib/lp/registry/browser/distributionsourcepackage.py	2015-06-11 10:29:28 +0000
@@ -49,6 +49,7 @@
     )
 from lp.bugs.interfaces.bugtask import BugTaskStatus
 from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
+from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
 from lp.registry.browser import add_subscribe_link
 from lp.registry.browser.pillar import PillarBugsMenu
 from lp.registry.interfaces.distributionsourcepackage import (
@@ -178,7 +179,7 @@
 
 class DistributionSourcePackageNavigation(Navigation,
     BugTargetTraversalMixin, HasCustomLanguageCodesTraversalMixin,
-    QuestionTargetTraversalMixin,
+    QuestionTargetTraversalMixin, TargetDefaultVCSNavigationMixin,
     StructuralSubscriptionTargetTraversalMixin):
 
     usedfor = IDistributionSourcePackage

=== modified file 'lib/lp/registry/browser/persondistributionsourcepackage.py'
--- lib/lp/registry/browser/persondistributionsourcepackage.py	2015-02-06 13:37:58 +0000
+++ lib/lp/registry/browser/persondistributionsourcepackage.py	2015-06-11 10:29:28 +0000
@@ -16,6 +16,7 @@
 from zope.traversing.interfaces import IPathAdapter
 
 from lp.app.errors import NotFoundError
+from lp.code.browser.vcslisting import PersonTargetDefaultVCSNavigationMixin
 from lp.registry.interfaces.persondistributionsourcepackage import (
     IPersonDistributionSourcePackage,
     )
@@ -28,7 +29,8 @@
 from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 
 
-class PersonDistributionSourcePackageNavigation(Navigation):
+class PersonDistributionSourcePackageNavigation(
+        PersonTargetDefaultVCSNavigationMixin, Navigation):
     usedfor = IPersonDistributionSourcePackage
 
     def traverse(self, branch_name):

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2015-05-19 11:29:24 +0000
+++ lib/lp/testing/factory.py	2015-06-11 10:29:28 +0000
@@ -1079,7 +1079,8 @@
     def makeBranch(self, branch_type=None, owner=None,
                    name=None, product=_DEFAULT, url=_DEFAULT, registrant=None,
                    information_type=None, stacked_on=None,
-                   sourcepackage=None, reviewer=None, **optional_branch_args):
+                   sourcepackage=None, reviewer=None, target=None,
+                   **optional_branch_args):
         """Create and return a new, arbitrary Branch of the given type.
 
         Any parameters for `IBranchNamespace.createBranch` can be specified to
@@ -1091,6 +1092,15 @@
             owner = self.makePerson()
         if name is None:
             name = self.getUniqueString('branch')
+        if target is not None:
+            assert product is _DEFAULT
+            assert sourcepackage is None
+            if IProduct.providedBy(target):
+                product = target
+            elif ISourcePackage.providedBy(target):
+                sourcepackage = target
+            else:
+                raise AssertionError("Unknown target: %r" % target)
 
         if sourcepackage is None:
             if product is _DEFAULT:


Follow ups