← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~stevenk/launchpad/new-branch-search into lp:launchpad

 

Steve Kowalik has proposed merging lp:~stevenk/launchpad/new-branch-search into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #793830 in Launchpad itself: "Branch:+register-merge / Specification:+linkbranch time out due to substring matching many tables"
  https://bugs.launchpad.net/launchpad/+bug/793830

For more details, see:
https://code.launchpad.net/~stevenk/launchpad/new-branch-search/+merge/147840

Destroy the current branch search paradigm. Unfortunately, now I can't finish this description since I've killed myself for using the word 'paradigm'.

IBranchCollection.search(), its one friend and three other semi-related but unused methods have met their timely demise, replaced with search_branches which doesn't do stupid things like looking for the search terms against Product.name or SourcePackageName. I have declared that only used in tests is unused, and forced the tests to make use of other methods.

search_branches will only match against Branch.name, with a few shortcuts that will return singular branches if we're passed a URL, lp: shortform or unique_name.

The branch vocabularies have been rewritten substaintly to rely on search_branches, since all of the branch search functionality makes use of them.
-- 
https://code.launchpad.net/~stevenk/launchpad/new-branch-search/+merge/147840
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stevenk/launchpad/new-branch-search into lp:launchpad.
=== modified file 'lib/lp/code/browser/widgets/tests/test_branchpopupwidget.py'
--- lib/lp/code/browser/widgets/tests/test_branchpopupwidget.py	2012-01-01 02:58:52 +0000
+++ lib/lp/code/browser/widgets/tests/test_branchpopupwidget.py	2013-02-12 05:42:24 +0000
@@ -211,7 +211,7 @@
 
         # Make a popup restricted to a particular product.
         vocab = BranchRestrictedOnProductVocabulary(self.launch_bag.product)
-        self.assertEqual(vocab.product, self.launch_bag.product)
+        self.assertEqual(vocab.context, self.launch_bag.product)
         popup = self.makeBranchPopup(vocab)
 
         # Make a branch on a different product.

=== modified file 'lib/lp/code/feed/branch.py'
--- lib/lp/code/feed/branch.py	2011-12-30 07:32:58 +0000
+++ lib/lp/code/feed/branch.py	2013-02-12 05:42:24 +0000
@@ -162,15 +162,12 @@
 
         Only `self.quantity` revisions are returned.
         """
-        from lp.code.model.branch import Branch
         collection = self._getCollection().visibleByUser(
             None).withLifecycleStatus(*DEFAULT_BRANCH_STATUS_IN_LISTING)
         branches = collection.getBranches(eager_load=False)
         branches.order_by(
-            Desc(Branch.date_last_modified),
-            Asc(Branch.target_suffix),
-            Desc(Branch.lifecycle_status),
-            Asc(Branch.name))
+            Desc('Branch.date_last_modified'), Asc('Branch.target_suffix'),
+            Desc('Branch.lifecycle_status'), Asc('Branch.name'))
         branches.config(limit=self.quantity)
         return list(branches)
 

=== modified file 'lib/lp/code/interfaces/branchcollection.py'
--- lib/lp/code/interfaces/branchcollection.py	2013-01-07 02:40:55 +0000
+++ lib/lp/code/interfaces/branchcollection.py	2013-02-12 05:42:24 +0000
@@ -161,36 +161,15 @@
     def isPrivate():
         """Restrict the collection to private branches."""
 
-    def isExclusive():
-        """Restrict the collection to branches owned by exclusive people."""
-
     def isSeries():
         """Restrict the collection to branches those linked to series."""
 
     def ownedBy(person):
         """Restrict the collection to branches owned by 'person'."""
 
-    def ownedByTeamMember(person):
-        """Restrict the collection to branches owned by 'person' or a team
-        of which person is a member.
-        """
-
     def registeredBy(person):
         """Restrict the collection to branches registered by 'person'."""
 
-    def relatedTo(person):
-        """Restrict the collection to branches related to 'person'.
-
-        That is, branches that 'person' owns, registered or is subscribed to.
-        """
-
-    def search(search_term):
-        """Search the collection for branches matching 'search_term'.
-
-        :param search_term: A string.
-        :return: An `ICountableIterator`.
-        """
-
     def scanned():
         """Restrict the collection to branches that have been scanned."""
 

=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py	2013-01-16 06:41:43 +0000
+++ lib/lp/code/model/branch.py	2013-02-12 05:42:24 +0000
@@ -147,6 +147,7 @@
     validate_person,
     validate_public_person,
     )
+from lp.registry.interfaces.role import IPersonRoles
 from lp.registry.interfaces.sharingjob import (
     IRemoveArtifactSubscriptionsJobSource,
     )
@@ -1630,6 +1631,9 @@
     if user is None:
         return [public_branch_filter]
 
+    if IPersonRoles(user).in_admin:
+        return []
+
     artifact_grant_query = Coalesce(
         ArrayIntersects(
             SQL('%s.access_grants' % branch_class.__storm_table__),

=== modified file 'lib/lp/code/model/branchcollection.py'
--- lib/lp/code/model/branchcollection.py	2012-10-18 19:06:48 +0000
+++ lib/lp/code/model/branchcollection.py	2013-02-12 05:42:24 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Implementations of `IBranchCollection`."""
@@ -6,11 +6,13 @@
 __metaclass__ = type
 __all__ = [
     'GenericBranchCollection',
+    'search_branches',
     ]
 
 from collections import defaultdict
 from functools import partial
 from operator import attrgetter
+from urlparse import urlparse
 
 from lazr.restful.utils import safe_hasattr
 from storm.expr import (
@@ -20,10 +22,8 @@
     In,
     Join,
     LeftJoin,
-    Or,
     Select,
     SQL,
-    Union,
     With,
     )
 from storm.info import ClassAlias
@@ -63,15 +63,14 @@
 from lp.code.model.codereviewvote import CodeReviewVoteReference
 from lp.code.model.revision import Revision
 from lp.code.model.seriessourcepackagebranch import SeriesSourcePackageBranch
-from lp.registry.enums import EXCLUSIVE_TEAM_POLICY
+from lp.registry.interfaces.distroseries import IDistroSeries
+from lp.registry.interfaces.person import IPerson
+from lp.registry.interfaces.product import IProduct
+from lp.registry.interfaces.sourcepackagename import ISourcePackageName
 from lp.registry.model.distribution import Distribution
 from lp.registry.model.distroseries import DistroSeries
-from lp.registry.model.person import (
-    Owner,
-    Person,
-    )
+from lp.registry.model.person import Person
 from lp.registry.model.product import Product
-from lp.registry.model.sourcepackagename import SourcePackageName
 from lp.registry.model.teammembership import TeamParticipation
 from lp.services.database.bulk import (
     load_referencing,
@@ -87,7 +86,6 @@
 from lp.services.database.sqlbase import quote
 from lp.services.propertycache import get_property_cache
 from lp.services.searchbuilder import any
-from lp.services.webapp.vocabulary import CountableIterator
 
 
 class GenericBranchCollection:
@@ -614,13 +612,6 @@
         return self._filterBy([
             Branch.information_type.is_in(PRIVATE_INFORMATION_TYPES)])
 
-    def isExclusive(self):
-        """See `IBranchCollection`."""
-        return self._filterBy(
-            [Person.membership_policy.is_in(EXCLUSIVE_TEAM_POLICY)],
-            table=Person,
-            join=Join(Person, Branch.ownerID == Person.id))
-
     def isSeries(self):
         """See `IBranchCollection`."""
         # ProductSeries import's this module.
@@ -634,85 +625,10 @@
         """See `IBranchCollection`."""
         return self._filterBy([Branch.owner == person], symmetric=False)
 
-    def ownedByTeamMember(self, person):
-        """See `IBranchCollection`."""
-        subquery = Select(
-            TeamParticipation.teamID,
-            where=TeamParticipation.personID == person.id)
-        filter = [In(Branch.ownerID, subquery)]
-
-        return self._filterBy(filter, symmetric=False)
-
     def registeredBy(self, person):
         """See `IBranchCollection`."""
         return self._filterBy([Branch.registrant == person], symmetric=False)
 
-    def relatedTo(self, person):
-        """See `IBranchCollection`."""
-        return self._filterBy(
-            [Branch.id.is_in(
-                Union(
-                    Select(Branch.id, Branch.owner == person),
-                    Select(Branch.id, Branch.registrant == person),
-                    Select(Branch.id,
-                           And(BranchSubscription.person == person,
-                               BranchSubscription.branch == Branch.id))))],
-            symmetric=False)
-
-    def _getExactMatch(self, search_term):
-        """Return the exact branch that 'search_term' matches, or None."""
-        search_term = search_term.rstrip('/')
-        branch_set = getUtility(IBranchLookup)
-        branch = branch_set.getByUniqueName(search_term)
-        if branch is None:
-            branch = branch_set.getByUrl(search_term)
-        return branch
-
-    def search(self, search_term):
-        """See `IBranchCollection`."""
-        # XXX: JonathanLange 2009-02-23 bug 372591: This matches the old
-        # search algorithm that used to live in vocabularies/dbojects.py. It's
-        # not actually very good -- really it should match based on substrings
-        # of the unique name and sort based on relevance.
-        branch = self._getExactMatch(search_term)
-        if branch is not None:
-            if branch in self.getBranches(eager_load=False):
-                return CountableIterator(1, [branch])
-            else:
-                return CountableIterator(0, [])
-        like_term = '%' + search_term + '%'
-        # Match the Branch name or the URL.
-        queries = [Select(Branch.id,
-                          Or(Branch.name.like(like_term),
-                             Branch.url == search_term))]
-        # Match the product name.
-        if 'product' not in self._exclude_from_search:
-            queries.append(Select(
-                Branch.id,
-                And(Branch.product == Product.id,
-                    Product.name.like(like_term))))
-
-        # Match the owner name.
-        queries.append(Select(
-            Branch.id,
-            And(Branch.owner == Owner.id, Owner.name.like(like_term))))
-
-        # Match the package bits.
-        queries.append(
-            Select(Branch.id,
-                   And(Branch.sourcepackagename == SourcePackageName.id,
-                       Branch.distroseries == DistroSeries.id,
-                       DistroSeries.distribution == Distribution.id,
-                       Or(SourcePackageName.name.like(like_term),
-                          DistroSeries.name.like(like_term),
-                          Distribution.name.like(like_term)))))
-
-        # Get the results.
-        collection = self._filterBy([Branch.id.is_in(Union(*queries))])
-        results = collection.getBranches(eager_load=False).order_by(
-            Branch.name, Branch.id)
-        return CountableIterator(results.count(), results)
-
     def scanned(self):
         """See `IBranchCollection`."""
         return self._filterBy([Branch.last_scanned != None])
@@ -863,3 +779,67 @@
         raise InvalidFilter(
             "Cannot filter for branches visible by user %r, already "
             "filtering for %r" % (person, self._user))
+
+
+def search_branches(context, user, search_term, extra_joins=[],
+                    extra_clauses=[]):
+    store = IStore(Branch)
+
+    # If the search_term is a full URL, grab the branch and return it.
+    if search_term and search_term.startswith('lp:'):
+        return _known_visible_branch(
+            getUtility(IBranchLookup).getByUrl(search_term), context, user) 
+    if search_term and search_term.startswith('http'):
+        # Look up the branch by its URL.
+        branch_url = getUtility(IBranchLookup).getByUrl(search_term)
+        if branch_url:
+            return _known_visible_branch(branch_url, context, user)
+        # If that fails, strip off the path and search by its unique name.
+        path = urlparse(search_term)[2][1:]
+        return _known_visible_branch(
+            getUtility(IBranchLookup).getByUniqueName(path), context, user)
+    if search_term and search_term.startswith('~'):
+        return _known_visible_branch(
+            getUtility(IBranchLookup).getByUniqueName(search_term), context,
+            user)
+
+    clauses = get_branch_privacy_filter(user)
+    if IProduct.providedBy(context):
+        col = Branch.productID
+    elif IDistroSeries.providedBy(context):
+        col = Branch.distroseries_id
+    elif ISourcePackageName.providedBy(context):
+        col = Branch.sourcepackagename_id
+    elif IPerson.providedBy(context):
+        extra_joins.extend([
+            Join(Person, Person.id == Branch.ownerID),
+            Join(
+                TeamParticipation, TeamParticipation.personID == context.id)])
+        clauses.append(Branch.ownerID == TeamParticipation.teamID)
+        col = None
+    else:
+        col = None
+    if col:
+        clauses.append(col == context.id)
+    if search_term:
+        clauses.append(Branch.name.like(search_term))
+    origin = [Branch]
+    if extra_joins:
+        origin.extend(extra_joins)
+    if extra_clauses:
+        clauses.extend(extra_clauses)
+
+    return list(store.using(*origin).find(Branch, *clauses))
+
+
+def _known_visible_branch(branch, context, user):
+    if branch is None:
+        return []
+    elif branch.visibleByUser(user):
+        if (context and IProduct.providedBy(context)
+            and branch.product != context):
+            return []
+        if user:
+            get_property_cache(branch)._known_viewers = [user.id]
+        return [branch]
+    return []

=== modified file 'lib/lp/code/model/tests/test_branchcollection.py'
--- lib/lp/code/model/tests/test_branchcollection.py	2012-10-18 19:20:49 +0000
+++ lib/lp/code/model/tests/test_branchcollection.py	2013-02-12 05:42:24 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for branch collections."""
@@ -31,19 +31,18 @@
     )
 from lp.code.interfaces.codehosting import LAUNCHPAD_SERVICES
 from lp.code.model.branch import Branch
-from lp.code.model.branchcollection import GenericBranchCollection
+from lp.code.model.branchcollection import (
+    GenericBranchCollection,
+    search_branches,
+    )
 from lp.code.tests.helpers import remove_all_sample_data_branches
 from lp.registry.enums import PersonVisibility
 from lp.registry.interfaces.person import TeamMembershipPolicy
 from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.services.database.interfaces import (
-    DEFAULT_FLAVOR,
-    IStoreSelector,
-    MAIN_STORE,
-    )
+from lp.services.database.lpstorm import IStore
+from lp.services.webapp.publisher import canonical_url
 from lp.testing import (
     person_logged_in,
-    run_with_login,
     StormStatementRecorder,
     TestCaseWithFactory,
     )
@@ -93,10 +92,9 @@
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        TestCaseWithFactory.setUp(self)
+        super(TestGenericBranchCollection, self).setUp()
         remove_all_sample_data_branches()
-        self.store = getUtility(IStoreSelector).get(
-            MAIN_STORE, DEFAULT_FLAVOR)
+        self.store = IStore(Branch)
 
     def test_provides_branchcollection(self):
         # `GenericBranchCollection` provides the `IBranchCollection`
@@ -257,16 +255,6 @@
         collection = self.all_branches.ownedBy(branch.owner)
         self.assertEqual([branch], list(collection.getBranches()))
 
-    def test_ownedByTeamMember(self):
-        # 'ownedBy' returns a new collection restricted to branches owned by
-        # any team of which the given person is a member.
-        person = self.factory.makePerson()
-        team = self.factory.makeTeam(members=[person])
-        branch = self.factory.makeAnyBranch(owner=team)
-        self.factory.makeAnyBranch()
-        collection = self.all_branches.ownedByTeamMember(person)
-        self.assertEqual([branch], list(collection.getBranches()))
-
     def test_in_product(self):
         # 'inProduct' returns a new collection restricted to branches in the
         # given product.
@@ -291,38 +279,6 @@
         collection = self.all_branches.inProject(project)
         self.assertEqual([branch], list(collection.getBranches()))
 
-    def test_isExclusive(self):
-        # 'isExclusive' is restricted to branches owned by exclusive
-        # teams and users.
-        user = self.factory.makePerson()
-        team = self.factory.makeTeam(
-            membership_policy=TeamMembershipPolicy.RESTRICTED)
-        other_team = self.factory.makeTeam(
-            membership_policy=TeamMembershipPolicy.OPEN)
-        team_branch = self.factory.makeAnyBranch(owner=team)
-        user_branch = self.factory.makeAnyBranch(owner=user)
-        self.factory.makeAnyBranch(owner=other_team)
-        collection = self.all_branches.isExclusive()
-        self.assertContentEqual(
-            [team_branch, user_branch], list(collection.getBranches()))
-
-    def test_inProduct_and_isExclusive(self):
-        # 'inProduct' and 'isExclusive' can combine to form a collection that
-        # is restricted to branches of a particular product owned exclusive
-        # teams and users.
-        team = self.factory.makeTeam(
-            membership_policy=TeamMembershipPolicy.RESTRICTED)
-        other_team = self.factory.makeTeam(
-            membership_policy=TeamMembershipPolicy.OPEN)
-        product = self.factory.makeProduct()
-        branch = self.factory.makeProductBranch(product=product, owner=team)
-        self.factory.makeAnyBranch(owner=team)
-        self.factory.makeProductBranch(product=product, owner=other_team)
-        collection = self.all_branches.inProduct(product).isExclusive()
-        self.assertEqual([branch], list(collection.getBranches()))
-        collection = self.all_branches.isExclusive().inProduct(product)
-        self.assertEqual([branch], list(collection.getBranches()))
-
     def test_isSeries(self):
         # 'isSeries' is restricted to branches linked to product series.
         series = self.factory.makeProductSeries()
@@ -379,24 +335,6 @@
         collection = self.all_branches.ownedBy(person).isPrivate()
         self.assertEqual([branch], list(collection.getBranches()))
 
-    def test_ownedByTeamMember_and_inProduct(self):
-        # 'ownedBy' and 'inProduct' can combine to form a collection that is
-        # restricted to branches of a particular product owned by a particular
-        # person or team of which the person is a member.
-        person = self.factory.makePerson()
-        team = self.factory.makeTeam(members=[person])
-        product = self.factory.makeProduct()
-        branch = self.factory.makeProductBranch(product=product, owner=person)
-        branch2 = self.factory.makeProductBranch(product=product, owner=team)
-        self.factory.makeAnyBranch(owner=person)
-        self.factory.makeProductBranch(product=product)
-        product_branches = self.all_branches.inProduct(product)
-        collection = product_branches.ownedByTeamMember(person)
-        self.assertContentEqual([branch, branch2], collection.getBranches())
-        person_branches = self.all_branches.ownedByTeamMember(person)
-        collection = person_branches.inProduct(product)
-        self.assertContentEqual([branch, branch2], collection.getBranches())
-
     def test_in_source_package(self):
         # 'inSourcePackage' returns a new collection that only has branches in
         # the given source package.
@@ -560,34 +498,6 @@
         collection = self.all_branches.subscribedBy(subscriber)
         self.assertEqual([branch], list(collection.getBranches()))
 
-    def test_relatedTo(self):
-        # 'relatedTo' returns a collection that has all branches that a user
-        # owns, is subscribed to or registered. No other branches are in this
-        # collection.
-        person = self.factory.makePerson()
-        team = self.factory.makeTeam(person)
-        owned_branch = self.factory.makeAnyBranch(owner=person)
-        # Unsubscribe the owner, to demonstrate that we show owned branches
-        # even if they aren't subscribed.
-        owned_branch.unsubscribe(person, person)
-        # Subscribe two other people to the owned branch to make sure
-        # that the BranchSubscription join is doing it right.
-        self.factory.makeBranchSubscription(branch=owned_branch)
-        self.factory.makeBranchSubscription(branch=owned_branch)
-
-        registered_branch = self.factory.makeAnyBranch(
-            owner=team, registrant=person)
-        subscribed_branch = self.factory.makeAnyBranch()
-        subscribed_branch.subscribe(
-            person, BranchSubscriptionNotificationLevel.NOEMAIL,
-            BranchSubscriptionDiffSize.NODIFF,
-            CodeReviewNotificationLevel.NOEMAIL,
-            person)
-        related_branches = self.all_branches.relatedTo(person)
-        self.assertEqual(
-            sorted([owned_branch, registered_branch, subscribed_branch]),
-            sorted(related_branches.getBranches()))
-
     def test_withBranchType(self):
         hosted_branch1 = self.factory.makeAnyBranch(
             branch_type=BranchType.HOSTED)
@@ -1118,190 +1028,6 @@
         self.assertEqual([proposal], list(proposals))
 
 
-class TestSearch(TestCaseWithFactory):
-    """Tests for IBranchCollection.search()."""
-
-    layer = DatabaseFunctionalLayer
-
-    def setUp(self):
-        TestCaseWithFactory.setUp(self)
-        remove_all_sample_data_branches()
-        self.collection = getUtility(IAllBranches)
-
-    def test_exact_match_unique_name(self):
-        # If you search for a unique name of a branch that exists, you'll get
-        # a single result with a branch with that branch name.
-        branch = self.factory.makeAnyBranch()
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search(branch.unique_name)
-        self.assertEqual([branch], list(search_results))
-
-    def test_unique_name_match_not_in_collection(self):
-        # If you search for a unique name of a branch that does not exist,
-        # you'll get an empty result set.
-        branch = self.factory.makeAnyBranch()
-        collection = self.collection.inProduct(self.factory.makeProduct())
-        search_results = collection.search(branch.unique_name)
-        self.assertEqual([], list(search_results))
-
-    def test_exact_match_remote_url(self):
-        # If you search for the remote URL of a branch, and there's a branch
-        # with that URL, you'll get a single result with a branch with that
-        # branch name.
-        branch = self.factory.makeAnyBranch(branch_type=BranchType.MIRRORED)
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search(branch.url)
-        self.assertEqual([branch], list(search_results))
-
-    def test_exact_match_launchpad_url(self):
-        # If you search for the Launchpad URL of a branch, and there is a
-        # branch with that URL, then you get a single result with that branch.
-        branch = self.factory.makeAnyBranch()
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search(branch.codebrowse_url())
-        self.assertEqual([branch], list(search_results))
-
-    def test_exact_match_bzr_identity(self):
-        # If you search for the bzr identity of a branch, then you get a
-        # single result with that branch.
-        branch = self.factory.makeAnyBranch()
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search(branch.bzr_identity)
-        self.assertEqual([branch], list(search_results))
-
-    def test_exact_match_bzr_identity_development_focus(self):
-        # If you search for the development focus and it is set, you get a
-        # single result with the development focus branch.
-        fooix = self.factory.makeProduct(name='fooix')
-        branch = self.factory.makeProductBranch(product=fooix)
-        run_with_login(
-            fooix.owner, setattr, fooix.development_focus,
-            'branch', branch)
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search('lp://dev/fooix')
-        self.assertEqual([branch], list(search_results))
-
-    def test_bad_match_bzr_identity_development_focus(self):
-        # If you search for the development focus for a project where one
-        # isn't set, you get an empty search result.
-        fooix = self.factory.makeProduct(name='fooix')
-        self.factory.makeProductBranch(product=fooix)
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search('lp://dev/fooix')
-        self.assertEqual([], list(search_results))
-
-    def test_bad_match_bzr_identity_no_project(self):
-        # If you search for the development focus for a project where one
-        # isn't set, you get an empty search result.
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search('lp://dev/fooix')
-        self.assertEqual([], list(search_results))
-
-    def test_exact_match_url_trailing_slash(self):
-        # Sometimes, users are inconsiderately unaware of our arbitrary
-        # database restrictions and will put trailing slashes on their search
-        # queries. Rather bravely, we refuse to explode in this case.
-        branch = self.factory.makeAnyBranch()
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search(branch.codebrowse_url() + '/')
-        self.assertEqual([branch], list(search_results))
-
-    def test_match_exact_branch_name(self):
-        # search returns all branches with the same name as the search term.
-        branch1 = self.factory.makeAnyBranch(name='foo')
-        branch2 = self.factory.makeAnyBranch(name='foo')
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search('foo')
-        self.assertEqual(sorted([branch1, branch2]), sorted(search_results))
-
-    def test_match_sub_branch_name(self):
-        # search returns all branches which have a name of which the search
-        # term is a substring.
-        branch1 = self.factory.makeAnyBranch(name='afoo')
-        branch2 = self.factory.makeAnyBranch(name='foob')
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search('foo')
-        self.assertEqual(sorted([branch1, branch2]), sorted(search_results))
-
-    def test_match_exact_owner_name(self):
-        # search returns all branches which have an owner with a name matching
-        # the server.
-        person = self.factory.makePerson(name='foo')
-        branch1 = self.factory.makeAnyBranch(owner=person)
-        branch2 = self.factory.makeAnyBranch(owner=person)
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search('foo')
-        self.assertEqual(sorted([branch1, branch2]), sorted(search_results))
-
-    def test_match_sub_owner_name(self):
-        # search returns all branches that have an owner name where the search
-        # term is a substring of the owner name.
-        person1 = self.factory.makePerson(name='foom')
-        branch1 = self.factory.makeAnyBranch(owner=person1)
-        person2 = self.factory.makePerson(name='afoo')
-        branch2 = self.factory.makeAnyBranch(owner=person2)
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search('foo')
-        self.assertEqual(sorted([branch1, branch2]), sorted(search_results))
-
-    def test_match_exact_product_name(self):
-        # search returns all branches that have a product name where the
-        # product name is the same as the search term.
-        product = self.factory.makeProduct(name='foo')
-        branch1 = self.factory.makeAnyBranch(product=product)
-        branch2 = self.factory.makeAnyBranch(product=product)
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search('foo')
-        self.assertEqual(sorted([branch1, branch2]), sorted(search_results))
-
-    def test_match_sub_product_name(self):
-        # search returns all branches that have a product name where the
-        # search terms is a substring of the product name.
-        product1 = self.factory.makeProduct(name='foom')
-        branch1 = self.factory.makeProductBranch(product=product1)
-        product2 = self.factory.makeProduct(name='afoo')
-        branch2 = self.factory.makeProductBranch(product=product2)
-        self.factory.makeAnyBranch()
-        search_results = self.collection.search('foo')
-        self.assertEqual(sorted([branch1, branch2]), sorted(search_results))
-
-    def test_match_sub_distro_name(self):
-        # search returns all branches that have a distro name where the search
-        # term is a substring of the distro name.
-        branch = self.factory.makePackageBranch()
-        self.factory.makeAnyBranch()
-        search_term = branch.distribution.name[1:]
-        search_results = self.collection.search(search_term)
-        self.assertEqual([branch], list(search_results))
-
-    def test_match_sub_distroseries_name(self):
-        # search returns all branches that have a distro series with a name
-        # that the search term is a substring of.
-        branch = self.factory.makePackageBranch()
-        self.factory.makeAnyBranch()
-        search_term = branch.distroseries.name[1:]
-        search_results = self.collection.search(search_term)
-        self.assertEqual([branch], list(search_results))
-
-    def test_match_sub_sourcepackage_name(self):
-        # search returns all branches that have a source package with a name
-        # that contains the search term.
-        branch = self.factory.makePackageBranch()
-        self.factory.makeAnyBranch()
-        search_term = branch.sourcepackagename.name[1:]
-        search_results = self.collection.search(search_term)
-        self.assertEqual([branch], list(search_results))
-
-    def test_dont_match_product_if_in_product(self):
-        # If the container is restricted to the product, then we don't match
-        # the product name.
-        product = self.factory.makeProduct('foo')
-        branch1 = self.factory.makeProductBranch(product=product, name='foo')
-        self.factory.makeProductBranch(product=product, name='bar')
-        search_results = self.collection.inProduct(product).search('foo')
-        self.assertEqual([branch1], list(search_results))
-
-
 class TestGetTeamsWithBranches(TestCaseWithFactory):
     """Test the BranchCollection.getTeamsWithBranches method."""
 
@@ -1403,3 +1129,58 @@
             *DEFAULT_BRANCH_STATUS_IN_LISTING)
         person_count, team_count = collection.ownerCounts()
         self.assertEqual(1, person_count)
+
+
+class TestBranchSearch(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_search_branches_with_lp_url(self):
+        branch = self.factory.makeBranch()
+        lp_name = 'lp://dev/' + branch.unique_name
+        self.assertEqual([branch], search_branches(None, None, lp_name))
+
+    def test_search_branches_with_full_url(self):
+        branch = self.factory.makeBranch()
+        url = canonical_url(branch)
+        self.assertEqual([branch], search_branches(None, None, url))
+
+    def test_search_branches_with_unique_name(self):
+        branch = self.factory.makeBranch()
+        self.assertEqual(
+            [branch], search_branches(None, None, branch.unique_name))
+
+    def test_search_branches_anonymous_with_private_branch(self):
+        product = self.factory.makeProduct()
+        self.factory.makeBranch(
+            product=product, information_type=InformationType.USERDATA,
+            name='foobar')
+        self.assertEqual([], search_branches(product, None, 'foobar'))
+
+    def test_search_branches_user_with_hidden_private_branch(self):
+        product = self.factory.makeProduct()
+        user = self.factory.makePerson()
+        self.factory.makeBranch(
+            product=product, information_type=InformationType.USERDATA,
+            name='foobar')
+        self.assertEqual([], search_branches(product, user, 'foobar'))
+
+    def test_search_branches_branch_not_in_context(self):
+        other_product = self.factory.makeProduct()
+        self.factory.makeBranch(name='foobar')
+        self.assertEqual([], search_branches(other_product, None, 'foobar'))
+
+    def test_search_branches_private_branch(self):
+        product = self.factory.makeProduct()
+        owner = self.factory.makePerson()
+        branch = self.factory.makeBranch(
+            product=product, information_type=InformationType.USERDATA,
+            name='foobar', owner=owner)
+        self.assertEqual([branch], search_branches(product, owner, 'foobar'))
+
+    def test_search_branches_user_context(self):
+        owner = self.factory.makePerson()
+        branch = self.factory.makeBranch(owner=owner)
+        branch_2 = self.factory.makeBranch(owner=owner)
+        self.assertContentEqual(
+            [branch, branch_2], search_branches(owner, owner, ''))

=== modified file 'lib/lp/code/stories/feeds/xx-branch-atom.txt'
--- lib/lp/code/stories/feeds/xx-branch-atom.txt	2012-07-05 04:59:52 +0000
+++ lib/lp/code/stories/feeds/xx-branch-atom.txt	2013-02-12 05:42:24 +0000
@@ -99,16 +99,17 @@
 
 If a branch is marked private it will not be displayed.  The Landscape
 developers team has two branches which are both private.
+
     >>> from zope.component import getUtility
     >>> from zope.security.proxy import removeSecurityProxy
     >>> from lp.code.model.branch import Branch
     >>> from lp.code.interfaces.branchcollection import IAllBranches
     >>> from lp.registry.interfaces.person import IPersonSet
+    >>> from lp.registry.interfaces.product import IProductSet
     >>> login(ANONYMOUS)
-    >>> person_set = getUtility(IPersonSet)
-    >>> landscape_team = person_set.getByName('landscape-developers')
-    >>> test_user = person_set.getByEmail('test@xxxxxxxxxxxxx')
-    >>> branches = getUtility(IAllBranches).relatedTo(landscape_team)
+    >>> test_user = getUtility(IPersonSet).getByEmail('test@xxxxxxxxxxxxx')
+    >>> landscape = getUtility(IProductSet)['landscape']
+    >>> branches = getUtility(IAllBranches).inProduct(landscape)
     >>> branches = branches.visibleByUser(
     ...     test_user).getBranches().order_by(Branch.id)
     >>> for branch in branches:

=== modified file 'lib/lp/code/vocabularies/branch.py'
--- lib/lp/code/vocabularies/branch.py	2012-10-18 17:49:39 +0000
+++ lib/lp/code/vocabularies/branch.py	2013-02-12 05:42:24 +0000
@@ -1,9 +1,8 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Vocabularies that contain branches."""
 
-
 __metaclass__ = type
 
 __all__ = [
@@ -12,17 +11,20 @@
     'HostedBranchRestrictedOnOwnerVocabulary',
     ]
 
+from storm.locals import Join
 from zope.component import getUtility
 from zope.interface import implements
 from zope.schema.vocabulary import SimpleTerm
 
 from lp.code.enums import BranchType
 from lp.code.interfaces.branch import IBranch
-from lp.code.interfaces.branchcollection import IAllBranches
 from lp.code.model.branch import Branch
+from lp.code.model.branchcollection import search_branches
+from lp.registry.enums import EXCLUSIVE_TEAM_POLICY
 from lp.registry.interfaces.person import IPerson
 from lp.registry.interfaces.product import IProduct
 from lp.registry.interfaces.productseries import IProductSeries
+from lp.registry.model.person import Person
 from lp.services.webapp.interfaces import ILaunchBag
 from lp.services.webapp.vocabulary import (
     CountableIterator,
@@ -31,12 +33,8 @@
     )
 
 
-class BranchVocabularyBase(SQLObjectVocabularyBase):
-    """A base class for Branch vocabularies.
-
-    Override `BranchVocabularyBase._getCollection` to provide the collection
-    of branches which make up the vocabulary.
-    """
+class BranchVocabulary(SQLObjectVocabularyBase):
+    """A vocabulary for searching branches."""
 
     implements(IHugeVocabulary)
 
@@ -56,64 +54,45 @@
             return iter(search_results).next()
         raise LookupError(token)
 
-    def _getCollection(self):
-        """Return the collection of branches the vocabulary searches.
-
-        Subclasses MUST override and implement this.
-        """
-        raise NotImplementedError(self._getCollection)
-
-    def searchForTerms(self, query=None, vocab_filter=None):
+    def searchForTerms(self, query=None, vocab_filter=None, extra_joins=[],
+                       extra_clauses=[]):
         """See `IHugeVocabulary`."""
-        logged_in_user = getUtility(ILaunchBag).user
-        collection = self._getCollection().visibleByUser(logged_in_user)
-        if query is None:
-            branches = collection.getBranches(eager_load=False)
-        else:
-            branches = collection.search(query)
-        return CountableIterator(branches.count(), branches, self.toTerm)
+        user = getUtility(ILaunchBag).user
+        branches = search_branches(
+            self.context, user, query, extra_joins=extra_joins,
+            extra_clauses=extra_clauses)
+        return CountableIterator(len(branches), branches, self.toTerm)
 
     def __len__(self):
         """See `IVocabulary`."""
         return self.search().count()
 
 
-class BranchVocabulary(BranchVocabularyBase):
-    """A vocabulary for searching branches.
-
-    The name and URL of the branch, the name of the product, and the
-    name of the registrant of the branches is checked for the entered
-    value.
-    """
-
-    def _getCollection(self):
-        return getUtility(IAllBranches)
-
-
-class BranchRestrictedOnProductVocabulary(BranchVocabularyBase):
-    """A vocabulary for searching branches restricted on product.
-
-    The query entered checks the name or URL of the branch, or the
-    name of the registrant of the branch.
-    """
+class BranchRestrictedOnProductVocabulary(BranchVocabulary):
+    """A vocabulary for searching branches restricted on product."""
 
     def __init__(self, context=None):
-        BranchVocabularyBase.__init__(self, context)
+        super(BranchRestrictedOnProductVocabulary, self).__init__(context)
         if IProduct.providedBy(self.context):
-            self.product = self.context
+            self.context = context
         elif IProductSeries.providedBy(self.context):
-            self.product = self.context.product
+            self.context = context.product
         elif IBranch.providedBy(self.context):
-            self.product = self.context.product
+            self.context = context.product
         else:
             # An unexpected type.
             raise AssertionError('Unexpected context type')
 
-    def _getCollection(self):
-        return getUtility(IAllBranches).inProduct(self.product).isExclusive()
-
-
-class HostedBranchRestrictedOnOwnerVocabulary(BranchVocabularyBase):
+    def searchForTerms(self, query=None, vocab_filter=None):
+        extra_joins = [Join(Person, Person.id == Branch.ownerID)]
+        extra_clauses = [
+            Person.membership_policy.is_in(EXCLUSIVE_TEAM_POLICY)]
+        return super(BranchRestrictedOnProductVocabulary, self).searchForTerms(
+            query, vocab_filter, extra_joins=extra_joins,
+            extra_clauses=extra_clauses)
+
+
+class HostedBranchRestrictedOnOwnerVocabulary(BranchVocabulary):
     """A vocabulary for hosted branches owned by the current user.
 
     These are branches that the user either owns themselves or which are
@@ -123,11 +102,12 @@
     def __init__(self, context=None):
         """Pass a Person as context, or anything else for the current user."""
         super(HostedBranchRestrictedOnOwnerVocabulary, self).__init__(context)
-        if IPerson.providedBy(self.context):
-            self.user = context
-        else:
-            self.user = getUtility(ILaunchBag).user
+        if not IPerson.providedBy(self.context):
+            self.context = getUtility(ILaunchBag).user
 
-    def _getCollection(self):
-        owned_branches = getUtility(IAllBranches).ownedByTeamMember(self.user)
-        return owned_branches.withBranchType(BranchType.HOSTED)
+    def searchForTerms(self, query=None, vocab_filter=None):
+        extra_clauses = [Branch.branch_type == BranchType.HOSTED]
+        return super(
+            HostedBranchRestrictedOnOwnerVocabulary, self).searchForTerms(
+                query, vocab_filter, extra_joins=[],
+                extra_clauses=extra_clauses)

=== modified file 'lib/lp/code/vocabularies/tests/branch.txt'
--- lib/lp/code/vocabularies/tests/branch.txt	2012-04-30 13:53:06 +0000
+++ lib/lp/code/vocabularies/tests/branch.txt	2013-02-12 05:42:24 +0000
@@ -32,16 +32,6 @@
     ~stevea/thunderbird/main
     ~vcs-imports/evolution/main
 
-    >>> print_vocab_branches(branch_vocabulary, 'vcs-imports')
-    ~vcs-imports/evolution/import
-    ~vcs-imports/evolution/main
-    ~vcs-imports/gnome-terminal/import
-
-    >>> print_vocab_branches(branch_vocabulary, 'evolution')
-    ~carlos/evolution/2.0
-    ~vcs-imports/evolution/import
-    ~vcs-imports/evolution/main
-
 A search with the full branch unique name should also find the branch.
 
     >>> print_vocab_branches(branch_vocabulary, '~name12/firefox/main')
@@ -107,9 +97,6 @@
     >>> print_vocab_branches(branch_vocabulary, 'main')
     ~name12/gnome-terminal/main
 
-    >>> print_vocab_branches(branch_vocabulary, 'vcs-imports')
-    ~vcs-imports/gnome-terminal/import
-
 If a full unique name is entered that has a different product, the
 branch is not part of the vocabulary.
 

=== modified file 'lib/lp/code/vocabularies/tests/test_branch_vocabularies.py'
--- lib/lp/code/vocabularies/tests/test_branch_vocabularies.py	2012-10-18 17:49:39 +0000
+++ lib/lp/code/vocabularies/tests/test_branch_vocabularies.py	2013-02-12 05:42:24 +0000
@@ -1,11 +1,10 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test the branch vocabularies."""
 
 __metaclass__ = type
 
-from unittest import TestCase
 
 from zope.component import getUtility
 
@@ -16,99 +15,36 @@
     )
 from lp.registry.enums import TeamMembershipPolicy
 from lp.registry.interfaces.product import IProductSet
-from lp.testing import (
-    ANONYMOUS,
-    login,
-    logout,
-    )
-from lp.testing.factory import LaunchpadObjectFactory
+from lp.testing import TestCaseWithFactory
 from lp.testing.layers import DatabaseFunctionalLayer
 
 
-class BranchVocabTestCase(TestCase):
-    """A base class for the branch vocabulary test cases."""
-    layer = DatabaseFunctionalLayer
-
-    def setUp(self):
-        # Set up the anonymous security interaction.
-        login(ANONYMOUS)
-
-    def tearDown(self):
-        logout()
-
-
-class TestBranchVocabulary(BranchVocabTestCase):
+class TestBranchVocabulary(TestCaseWithFactory):
     """Test that the BranchVocabulary behaves as expected."""
 
+    layer = DatabaseFunctionalLayer
+
     def setUp(self):
-        BranchVocabTestCase.setUp(self)
+        super(TestBranchVocabulary, self).setUp()
         self._createBranches()
         self.vocab = BranchVocabulary(context=None)
 
     def _createBranches(self):
-        factory = LaunchpadObjectFactory()
-        widget = factory.makeProduct(name='widget')
-        sprocket = factory.makeProduct(name='sprocket')
-        # Scotty's branches.
-        scotty = factory.makePerson(name='scotty')
-        factory.makeProductBranch(
+        widget = self.factory.makeProduct(name='widget')
+        sprocket = self.factory.makeProduct(name='sprocket')
+        scotty = self.factory.makePerson(name='scotty')
+        self.factory.makeProductBranch(
             owner=scotty, product=widget, name='fizzbuzz')
-        factory.makeProductBranch(
+        self.factory.makeProductBranch(
             owner=scotty, product=widget, name='mountain')
-        factory.makeProductBranch(
+        self.factory.makeProductBranch(
             owner=scotty, product=sprocket, name='fizzbuzz')
-        # Spotty's branches.
-        spotty = factory.makePerson(name='spotty')
-        factory.makeProductBranch(
-            owner=spotty, product=widget, name='hill')
-        factory.makeProductBranch(
-            owner=spotty, product=widget, name='sprocket')
-        # Sprocket's branches.
-        sprocket_person = factory.makePerson(name='sprocket')
-        factory.makeProductBranch(
-            owner=sprocket_person, product=widget, name='foo')
 
     def test_fizzbuzzBranches(self):
         """Return branches that match the string 'fizzbuzz'."""
         results = self.vocab.searchForTerms('fizzbuzz')
         expected = [
-            u'~scotty/sprocket/fizzbuzz',
-            u'~scotty/widget/fizzbuzz',
-            ]
-        branch_names = sorted([branch.token for branch in results])
-        self.assertEqual(expected, branch_names)
-
-    def test_widgetBranches(self):
-        """Searches match the product name too."""
-        results = self.vocab.searchForTerms('widget')
-        expected = [
-            u'~scotty/widget/fizzbuzz',
-            u'~scotty/widget/mountain',
-            u'~spotty/widget/hill',
-            u'~spotty/widget/sprocket',
-            u'~sprocket/widget/foo',
-            ]
-        branch_names = sorted([branch.token for branch in results])
-        self.assertEqual(expected, branch_names)
-
-    def test_spottyBranches(self):
-        """Searches also match the registrant name."""
-        results = self.vocab.searchForTerms('spotty')
-        expected = [
-            u'~spotty/widget/hill',
-            u'~spotty/widget/sprocket',
-            ]
-        branch_names = sorted([branch.token for branch in results])
-        self.assertEqual(expected, branch_names)
-
-    def test_crossAttributeBranches(self):
-        """The search checks name, product, and person."""
-        results = self.vocab.searchForTerms('rocket')
-        expected = [
-            u'~scotty/sprocket/fizzbuzz',
-            u'~spotty/widget/sprocket',
-            u'~sprocket/widget/foo',
-            ]
+            u'~scotty/sprocket/fizzbuzz', u'~scotty/widget/fizzbuzz']
         branch_names = sorted([branch.token for branch in results])
         self.assertEqual(expected, branch_names)
 
@@ -116,20 +52,15 @@
         # If there is a single search result that matches, use that
         # as the result.
         term = self.vocab.getTermByToken('mountain')
-        self.assertEqual(
-            '~scotty/widget/mountain',
-            term.value.unique_name)
+        self.assertEqual('~scotty/widget/mountain', term.value.unique_name)
 
     def test_multipleQueryResult(self):
         # If there are more than one search result, a LookupError is still
         # raised.
-        self.assertRaises(
-            LookupError,
-            self.vocab.getTermByToken,
-            'fizzbuzz')
-
-
-class TestRestrictedBranchVocabularyOnProduct(BranchVocabTestCase):
+        self.assertRaises(LookupError, self.vocab.getTermByToken, 'fizzbuzz')
+
+
+class TestRestrictedBranchVocabularyOnProduct(TestCaseWithFactory):
     """Test the BranchRestrictedOnProductVocabulary behaves as expected.
 
     When a BranchRestrictedOnProductVocabulary is used with a product the
@@ -137,8 +68,10 @@
     context.
     """
 
+    layer = DatabaseFunctionalLayer
+
     def setUp(self):
-        BranchVocabTestCase.setUp(self)
+        super(TestRestrictedBranchVocabularyOnProduct, self).setUp()
         self._createBranches()
         self.vocab = BranchRestrictedOnProductVocabulary(
             context=self._getVocabRestriction())
@@ -148,18 +81,17 @@
         return getUtility(IProductSet).getByName('widget')
 
     def _createBranches(self):
-        factory = LaunchpadObjectFactory()
-        test_product = factory.makeProduct(name='widget')
-        other_product = factory.makeProduct(name='sprocket')
-        person = factory.makePerson(name='scotty')
-        factory.makeProductBranch(
+        test_product = self.factory.makeProduct(name='widget')
+        other_product = self.factory.makeProduct(name='sprocket')
+        person = self.factory.makePerson(name='scotty')
+        self.factory.makeProductBranch(
             owner=person, product=test_product, name='main')
-        factory.makeProductBranch(
+        self.factory.makeProductBranch(
             owner=person, product=test_product, name='mountain')
-        factory.makeProductBranch(
+        self.factory.makeProductBranch(
             owner=person, product=other_product, name='main')
-        person = factory.makePerson(name='spotty')
-        factory.makeProductBranch(
+        person = self.factory.makePerson(name='spotty')
+        self.factory.makeProductBranch(
             owner=person, product=test_product, name='hill')
         self.product = test_product
 
@@ -169,23 +101,7 @@
         The result set should not show ~scotty/sprocket/main.
         """
         results = self.vocab.searchForTerms('main')
-        expected = [
-            u'~scotty/widget/main',
-            ]
-        branch_names = sorted([branch.token for branch in results])
-        self.assertEqual(expected, branch_names)
-
-    def test_ownersBranches(self):
-        """Look for branches owned by scotty.
-
-        The result set should not show ~scotty/sprocket/main.
-        """
-        results = self.vocab.searchForTerms('scotty')
-
-        expected = [
-            u'~scotty/widget/main',
-            u'~scotty/widget/mountain',
-            ]
+        expected = [u'~scotty/widget/main']
         branch_names = sorted([branch.token for branch in results])
         self.assertEqual(expected, branch_names)
 
@@ -193,26 +109,20 @@
         # If there is a single search result that matches, use that
         # as the result.
         term = self.vocab.getTermByToken('mountain')
-        self.assertEqual(
-            '~scotty/widget/mountain',
-            term.value.unique_name)
+        self.assertEqual('~scotty/widget/mountain', term.value.unique_name)
 
     def test_multipleQueryResult(self):
         # If there are more than one search result, a LookupError is still
         # raised.
-        self.assertRaises(
-            LookupError,
-            self.vocab.getTermByToken,
-            'scotty')
+        self.assertRaises(LookupError, self.vocab.getTermByToken, 'scotty')
 
     def test_does_not_contain_inclusive_teams(self):
-        factory = LaunchpadObjectFactory()
-        open_team = factory.makeTeam(name='open-team',
+        open_team = self.factory.makeTeam(name='open-team',
             membership_policy=TeamMembershipPolicy.OPEN)
-        delegated_team = factory.makeTeam(name='delegated-team',
+        delegated_team = self.factory.makeTeam(name='delegated-team',
             membership_policy=TeamMembershipPolicy.DELEGATED)
         for team in [open_team, delegated_team]:
-            factory.makeProductBranch(
+            self.factory.makeProductBranch(
                 owner=team, product=self.product, name='mountain')
         results = self.vocab.searchForTerms('mountain')
         branch_names = sorted([branch.token for branch in results])
@@ -230,5 +140,4 @@
 
     def _getVocabRestriction(self):
         """Restrict using a branch on widget."""
-        return getUtility(IBranchLookup).getByUniqueName(
-            '~spotty/widget/hill')
+        return getUtility(IBranchLookup).getByUniqueName('~spotty/widget/hill')

=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py	2013-02-07 06:10:38 +0000
+++ lib/lp/registry/model/product.py	2013-02-12 05:42:24 +0000
@@ -585,8 +585,6 @@
 
     @property
     def official_codehosting(self):
-        # XXX Need to remove official_codehosting column from Product
-        # table.
         return self.development_focus.branch is not None
 
     @property

=== modified file 'lib/lp/registry/services/sharingservice.py'
--- lib/lp/registry/services/sharingservice.py	2013-01-09 05:37:18 +0000
+++ lib/lp/registry/services/sharingservice.py	2013-02-12 05:42:24 +0000
@@ -35,7 +35,8 @@
 from lp.blueprints.model.specification import Specification
 from lp.bugs.interfaces.bugtask import IBugTaskSet
 from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
-from lp.code.interfaces.branchcollection import IAllBranches
+from lp.code.model.branch import Branch
+from lp.code.model.branchcollection import search_branches
 from lp.registry.enums import (
     BranchSharingPolicy,
     BugSharingPolicy,
@@ -217,10 +218,9 @@
         # Load the branches.
         branches = []
         if branch_ids:
-            all_branches = getUtility(IAllBranches)
-            wanted_branches = all_branches.visibleByUser(user).withIds(
-                *branch_ids)
-            branches = list(wanted_branches.getBranches())
+            clauses = [Branch.id.is_in(branch_ids)]
+            branches = search_branches(
+                None, user, '', extra_clauses=clauses)
         specifications = []
         if specification_ids:
             specifications = load(Specification, specification_ids)
@@ -331,10 +331,9 @@
         # Load the branches.
         visible_branches = []
         if branches_by_id:
-            all_branches = getUtility(IAllBranches)
-            wanted_branches = all_branches.visibleByUser(person).withIds(
-                *branches_by_id.keys())
-            visible_branches = list(wanted_branches.getBranches())
+            clauses = [Branch.id.is_in(branches_by_id.keys())]
+            visible_branches = search_branches(
+                None, person, '', extra_clauses=clauses)
 
         visible_specs = []
         if specifications:
@@ -367,9 +366,9 @@
         # Load the branches.
         invisible_branches = []
         if branches_by_id:
-            all_branches = getUtility(IAllBranches)
-            visible_branch_ids = all_branches.visibleByUser(person).withIds(
-                *branches_by_id.keys()).getBranchIds()
+            clauses = [Branch.id.is_in(branches_by_id.keys())]
+            visible_branch_ids = [branch.id for branch in search_branches(
+                None, person, '', extra_clauses=clauses)]
             invisible_branch_ids = (
                 set(branches_by_id.keys()).difference(visible_branch_ids))
             invisible_branches = [

=== modified file 'lib/lp/services/webapp/configure.zcml'
--- lib/lp/services/webapp/configure.zcml	2012-09-28 07:11:24 +0000
+++ lib/lp/services/webapp/configure.zcml	2013-02-12 05:42:24 +0000
@@ -418,11 +418,11 @@
       permission="zope.Public"
       />
 
-    <!-- Define the widget used by fields that use BranchVocabularyBase. -->
+    <!-- Define the widget used by fields that use BranchVocabulary. -->
     <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
       for="zope.schema.interfaces.IChoice
-        lp.code.vocabularies.branch.BranchVocabularyBase"
+        lp.code.vocabularies.branch.BranchVocabulary"
       provides="zope.app.form.interfaces.IInputWidget"
       factory="lp.code.browser.widgets.branch.BranchPopupWidget"
       permission="zope.Public"


Follow ups