← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~stub/launchpad/trivial into lp:launchpad

 

Stuart Bishop has proposed merging lp:~stub/launchpad/trivial into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #951401 in Launchpad itself: "parse-ppa-apache-logs failing (missing files)"
  https://bugs.launchpad.net/launchpad/+bug/951401

For more details, see:
https://code.launchpad.net/~stub/launchpad/trivial/+merge/137141

= Summary =

allocate-revision-karma.py is broken and causing trouble on production. This is Bug #1050191

== Proposed fix ==

Disable it.

== Pre-implementation notes ==

== LOC Rationale ==

== Implementation details ==

== Tests ==

== Demo and Q/A ==


= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  database/schema/patch-2209-39-0.sql
  database/schema/security.cfg
  cronscripts/nightly.sh

./cronscripts/nightly.sh
      30: Line exceeds 80 characters.
      34: Line exceeds 80 characters.
      37: Line exceeds 80 characters.
      46: Line exceeds 80 characters.
-- 
https://code.launchpad.net/~stub/launchpad/trivial/+merge/137141
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stub/launchpad/trivial into lp:launchpad.
=== modified file 'cronscripts/nightly.sh'
--- cronscripts/nightly.sh	2012-06-19 14:34:56 +0000
+++ cronscripts/nightly.sh	2012-11-30 10:13:01 +0000
@@ -29,8 +29,9 @@
 echo $(date): Expiring memberships >> $LOGFILE
 python -S flag-expired-memberships.py -q --log-file=DEBUG:$LOGDIR/flag-expired-memberships.log
 
-echo $(date): Allocating revision karma >> $LOGFILE
-python -S allocate-revision-karma.py -q --log-file=DEBUG:$LOGDIR/allocate-revision-karma.log
+## Disabled per Bug #1050191, StuartBishop 20121130
+## echo $(date): Allocating revision karma >> $LOGFILE
+## python -S allocate-revision-karma.py -q --log-file=DEBUG:$LOGDIR/allocate-revision-karma.log
 
 echo $(date): Recalculating karma >> $LOGFILE
 python -S foaf-update-karma-cache.py -q --log-file=INFO:$LOGDIR/foaf-update-karma-cache.log

=== modified file 'database/schema/Makefile'
=== modified file 'database/schema/comments.sql'
=== modified file 'lib/lp/answers/browser/question.py'
=== modified file 'lib/lp/answers/browser/questiontarget.py'
=== modified file 'lib/lp/app/browser/tales.py'
=== modified file 'lib/lp/app/doc/tales.txt'
=== modified file 'lib/lp/archiveuploader/tests/test_sync_notification.py'
=== modified file 'lib/lp/blueprints/browser/tests/test_specification.py'
=== modified file 'lib/lp/blueprints/interfaces/specification.py'
=== modified file 'lib/lp/blueprints/model/specification.py'
--- lib/lp/blueprints/model/specification.py	2012-11-26 08:33:03 +0000
+++ lib/lp/blueprints/model/specification.py	2012-11-30 10:13:01 +0000
@@ -3,7 +3,12 @@
 
 __metaclass__ = type
 __all__ = [
-    'get_specification_filters',
+<<<<<<< TREE
+    'get_specification_filters',
+=======
+    'get_specification_filters',
+    'get_specification_privacy_filter',
+>>>>>>> MERGE-SOURCE
     'HasSpecificationsMixin',
     'recursive_blocked_query',
     'recursive_dependent_query',
@@ -35,6 +40,7 @@
     LeftJoin,
     Not,
     Or,
+    Not,
     Select,
     )
 from storm.locals import (
@@ -112,7 +118,12 @@
     SQLBase,
     sqlvalues,
     )
-from lp.services.database.stormexpr import fti_search
+<<<<<<< TREE
+from lp.services.database.stormexpr import fti_search
+=======
+from lp.services.database.stormexpr import fti_search
+from lp.services.database.lpstorm import IStore
+>>>>>>> MERGE-SOURCE
 from lp.services.mail.helpers import get_contact_email_addresses
 from lp.services.propertycache import (
     cachedproperty,
@@ -988,7 +999,11 @@
         elif sort == SpecificationSort.DATE:
             return (Desc(Specification.datecreated), Specification.id)
 
+<<<<<<< TREE
     def _preload_specifications_people(self, tables, clauses):
+=======
+    def _preload_specifications_people(self, clauses):
+>>>>>>> MERGE-SOURCE
         """Perform eager loading of people and their validity for query.
 
         :param query: a string query generated in the 'specifications'
@@ -1029,7 +1044,11 @@
                     index += 1
                     decorator(person, column)
 
+<<<<<<< TREE
         results = Store.of(self).using(*tables).find(Specification, *clauses)
+=======
+        results = Store.of(self).find(Specification, *clauses)
+>>>>>>> MERGE-SOURCE
         return DecoratedResultSet(results, pre_iter_hook=cache_people)
 
     @property
@@ -1272,6 +1291,7 @@
         AccessPolicy,
         AccessPolicyGrantFlat,
         )
+<<<<<<< TREE
     tables = [
         Specification,
         LeftJoin(Product, Specification.productID == Product.id),
@@ -1354,3 +1374,148 @@
         Specification.definition_status ==
             SpecificationDefinitionStatus.APPROVED
     ))
+=======
+    public_specification_filter = (
+        Specification.information_type.is_in(PUBLIC_INFORMATION_TYPES))
+    if user is None:
+        return public_specification_filter
+    return Or(
+        public_specification_filter,
+        Specification.id.is_in(
+            Select(
+                Specification.id,
+                tables=(
+                    Specification,
+                    Join(
+                        AccessPolicy,
+                        And(
+                            Or(
+                                Specification.productID ==
+                                    AccessPolicy.product_id,
+                                Specification.distributionID ==
+                                    AccessPolicy.distribution_id),
+                            Specification.information_type ==
+                                AccessPolicy.type)),
+                    Join(
+                        AccessPolicyGrantFlat,
+                        AccessPolicy.id == AccessPolicyGrantFlat.policy_id),
+                    LeftJoin(
+                        AccessArtifact,
+                        AccessPolicyGrantFlat.abstract_artifact_id ==
+                            AccessArtifact.id),
+                    Join(
+                        TeamParticipation,
+                        And(
+                            TeamParticipation.team ==
+                                AccessPolicyGrantFlat.grantee_id,
+                            TeamParticipation.person == user))),
+                where=Or(
+                    AccessPolicyGrantFlat.abstract_artifact_id == None,
+                    AccessArtifact.specification_id == Specification.id))))
+
+
+def visible_specification_query(user):
+    """Return a Storm expression and list of tables for filtering
+    specifications by privacy.
+
+    :param user: A Person ID or a column reference.
+    :return: A tuple of tables, clauses to filter out specifications that the
+        user cannot see.
+    """
+    from lp.registry.model.product import Product
+    from lp.registry.model.accesspolicy import (
+        AccessArtifact,
+        AccessPolicy,
+        AccessPolicyGrantFlat,
+        )
+    tables = [
+        Specification,
+        LeftJoin(Product, Specification.productID == Product.id),
+        LeftJoin(AccessPolicy, And(
+            Or(Specification.productID == AccessPolicy.product_id,
+               Specification.distributionID ==
+               AccessPolicy.distribution_id),
+            Specification.information_type == AccessPolicy.type)),
+        LeftJoin(AccessPolicyGrantFlat,
+                 AccessPolicy.id == AccessPolicyGrantFlat.policy_id),
+        LeftJoin(
+            TeamParticipation,
+            And(AccessPolicyGrantFlat.grantee == TeamParticipation.teamID,
+                TeamParticipation.person == user)),
+        LeftJoin(AccessArtifact,
+                 AccessPolicyGrantFlat.abstract_artifact_id ==
+                 AccessArtifact.id)
+        ]
+    clauses = [
+        Or(Specification.information_type.is_in(PUBLIC_INFORMATION_TYPES),
+           And(AccessPolicyGrantFlat.id != None,
+               TeamParticipation.personID != None,
+               Or(AccessPolicyGrantFlat.abstract_artifact == None,
+                  AccessArtifact.specification_id == Specification.id))),
+        Or(Specification.product == None, Product.active == True)]
+    return tables, clauses
+
+
+def get_specification_filters(filter, assume_product_active=False):
+    """Return a list of Storm expressions for filtering Specifications.
+
+    :param filters: A collection of SpecificationFilter and/or strings.
+        Strings are used for text searches.
+    :param assume_product_active: If True, assume the Product is active,
+        instead of ensuring it is active.
+    """
+    from lp.registry.model.product import Product
+    clauses = []
+    # If Product is used, it must be active.
+    if not assume_product_active:
+        clauses.extend([Or(Specification.product == None,
+                        Not(Specification.productID.is_in(Select(Product.id,
+                        Product.active == False))))])
+    # ALL is the trump card.
+    if SpecificationFilter.ALL in filter:
+        return clauses
+    # Look for informational specs.
+    if SpecificationFilter.INFORMATIONAL in filter:
+        clauses.append(Specification.implementation_status ==
+                       SpecificationImplementationStatus.INFORMATIONAL)
+    # Filter based on completion.  See the implementation of
+    # Specification.is_complete() for more details.
+    if SpecificationFilter.COMPLETE in filter:
+        clauses.append(Specification.storm_completeness())
+    if SpecificationFilter.INCOMPLETE in filter:
+        clauses.append(Not(Specification.storm_completeness()))
+
+    # Filter for validity. If we want valid specs only, then we should exclude
+    # all OBSOLETE or SUPERSEDED specs.
+    if SpecificationFilter.VALID in filter:
+        clauses.append(Not(Specification.definition_status.is_in([
+            SpecificationDefinitionStatus.OBSOLETE,
+            SpecificationDefinitionStatus.SUPERSEDED,
+        ])))
+    # Filter for specification text.
+    for constraint in filter:
+        if isinstance(constraint, basestring):
+            # A string in the filter is a text search filter.
+            clauses.append(fti_search(Specification, constraint))
+    return clauses
+
+
+# NB NB If you change this definition, please update the equivalent
+# DB constraint Specification.specification_start_recorded_chk
+# We choose to define "started" as the set of delivery states NOT
+# in the values we select. Another option would be to say "anything less
+# than a threshold" and to comment the dbschema that "anything not
+# started should be less than the threshold". We'll see how maintainable
+# this is.
+spec_started_clause = Or(Not(Specification.implementation_status.is_in([
+    SpecificationImplementationStatus.UNKNOWN,
+    SpecificationImplementationStatus.NOTSTARTED,
+    SpecificationImplementationStatus.DEFERRED,
+    SpecificationImplementationStatus.INFORMATIONAL,
+    ])),
+    And(Specification.implementation_status ==
+            SpecificationImplementationStatus.INFORMATIONAL,
+        Specification.definition_status ==
+            SpecificationDefinitionStatus.APPROVED
+    ))
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/blueprints/model/sprint.py'
--- lib/lp/blueprints/model/sprint.py	2012-11-15 21:20:16 +0000
+++ lib/lp/blueprints/model/sprint.py	2012-11-30 10:13:01 +0000
@@ -155,8 +155,14 @@
         if len(statuses) > 0:
             query.append(Or(*statuses))
         # Filter for specification text
+<<<<<<< TREE
         query.extend(get_specification_filters(filter))
         return tables, query
+=======
+        query.extend(
+            get_specification_filters(filter, assume_product_active=True))
+        return tables, query
+>>>>>>> MERGE-SOURCE
 
     def all_specifications(self, user):
         return self.specifications(user, filter=[SpecificationFilter.ALL])

=== modified file 'lib/lp/blueprints/tests/test_specification.py'
=== modified file 'lib/lp/blueprints/tests/test_webservice.py'
=== modified file 'lib/lp/bugs/browser/bugalsoaffects.py'
=== modified file 'lib/lp/bugs/browser/bugtarget.py'
=== modified file 'lib/lp/bugs/browser/bugtask.py'
=== modified file 'lib/lp/bugs/browser/bugwatch.py'
=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_filebug.py'
=== modified file 'lib/lp/bugs/tests/test_bug_notification_recipients.py'
--- lib/lp/bugs/tests/test_bug_notification_recipients.py	2012-11-26 08:33:03 +0000
+++ lib/lp/bugs/tests/test_bug_notification_recipients.py	2012-11-30 10:13:01 +0000
@@ -10,6 +10,10 @@
     GreaterThan,
     )
 from zope.component import getUtility
+from testtools.matchers import (
+    Equals,
+    GreaterThan,
+    )
 
 from lp.app.enums import InformationType
 from lp.bugs.enums import BugNotificationLevel

=== modified file 'lib/lp/bugs/tests/test_bugnomination.py'
--- lib/lp/bugs/tests/test_bugnomination.py	2012-11-26 08:33:03 +0000
+++ lib/lp/bugs/tests/test_bugnomination.py	2012-11-30 10:13:01 +0000
@@ -5,15 +5,27 @@
 
 __metaclass__ = type
 
-from zope.component import getUtility
-
-from lp.app.errors import NotFoundError
-from lp.bugs.interfaces.bugnomination import (
-    BugNominationStatus,
-    BugNominationStatusError,
-    IBugNomination,
-    IBugNominationSet,
-    )
+<<<<<<< TREE
+from zope.component import getUtility
+
+from lp.app.errors import NotFoundError
+from lp.bugs.interfaces.bugnomination import (
+    BugNominationStatus,
+    BugNominationStatusError,
+    IBugNomination,
+    IBugNominationSet,
+    )
+=======
+from zope.component import getUtility
+
+from lp.app.errors import NotFoundError
+from lp.bugs.interfaces.bugnomination import (
+    BugNominationStatusError,
+    BugNominationStatus,
+    IBugNomination,
+    IBugNominationSet,
+    )
+>>>>>>> MERGE-SOURCE
 from lp.soyuz.interfaces.publishing import PackagePublishingStatus
 from lp.testing import (
     celebrity_logged_in,

=== modified file 'lib/lp/bugs/vocabularies.py'
--- lib/lp/bugs/vocabularies.py	2012-11-26 08:33:03 +0000
+++ lib/lp/bugs/vocabularies.py	2012-11-30 10:13:01 +0000
@@ -32,6 +32,7 @@
     )
 from zope.component import getUtility
 from zope.interface import implements
+from zope.security.proxy import removeSecurityProxy
 from zope.schema.interfaces import (
     IVocabulary,
     IVocabularyTokenized,

=== modified file 'lib/lp/code/browser/codeimport.py'
=== modified file 'lib/lp/code/browser/tests/test_branch.py'
=== modified file 'lib/lp/code/browser/tests/test_branchlisting.py'
=== modified file 'lib/lp/code/xmlrpc/codehosting.py'
--- lib/lp/code/xmlrpc/codehosting.py	2012-11-26 08:33:03 +0000
+++ lib/lp/code/xmlrpc/codehosting.py	2012-11-30 10:13:01 +0000
@@ -65,10 +65,17 @@
     IPersonSet,
     NoSuchPerson,
     )
-from lp.registry.interfaces.product import (
-    InvalidProductName,
-    NoSuchProduct,
-    )
+<<<<<<< TREE
+from lp.registry.interfaces.product import (
+    InvalidProductName,
+    NoSuchProduct,
+    )
+=======
+from lp.registry.interfaces.product import (
+    NoSuchProduct,
+    InvalidProductName,
+    )
+>>>>>>> MERGE-SOURCE
 from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
 from lp.services.scripts.interfaces.scriptactivity import IScriptActivitySet
 from lp.services.webapp import LaunchpadXMLRPCView

=== modified file 'lib/lp/codehosting/codeimport/tests/test_workermonitor.py'
=== modified file 'lib/lp/registry/browser/distroseries.py'
=== modified file 'lib/lp/registry/browser/person.py'
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py	2012-11-27 15:04:39 +0000
+++ lib/lp/registry/browser/product.py	2012-11-30 10:13:01 +0000
@@ -157,6 +157,7 @@
     PillarViewMixin,
     )
 from lp.registry.browser.productseries import get_series_branch_error
+from lp.registry.errors import CannotChangeInformationType
 from lp.registry.interfaces.pillar import IPillarNameSet
 from lp.registry.interfaces.product import (
     IProduct,
@@ -1465,7 +1466,10 @@
 
     @action("Change", name='change')
     def change_action(self, action, data):
-        self.updateContextFromData(data)
+        try:
+            self.updateContextFromData(data)
+        except CannotChangeInformationType as e:
+            self.setFieldError('information_type', str(e))
 
 
 class ProductValidationMixin:

=== modified file 'lib/lp/registry/browser/productseries.py'
=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py	2012-11-26 08:40:20 +0000
+++ lib/lp/registry/browser/sourcepackage.py	2012-11-30 10:13:01 +0000
@@ -67,10 +67,14 @@
     StepView,
     )
 from lp.app.browser.tales import CustomizableFormatter
+<<<<<<< TREE
 from lp.app.enums import (
     InformationType,
     ServiceUsage,
     )
+=======
+from lp.app.enums import InformationType, ServiceUsage
+>>>>>>> MERGE-SOURCE
 from lp.app.widgets.itemswidgets import LaunchpadRadioWidget
 from lp.bugs.browser.bugtask import BugTargetTraversalMixin
 from lp.registry.browser.product import ProjectAddStepOne

=== modified file 'lib/lp/registry/browser/tests/test_person.py'
--- lib/lp/registry/browser/tests/test_person.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/browser/tests/test_person.py	2012-11-30 10:13:01 +0000
@@ -22,6 +22,7 @@
 from lp.app.browser.lazrjs import TextAreaEditorWidget
 from lp.app.enums import InformationType
 from lp.app.errors import NotFoundError
+from lp.app.enums import InformationType
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.blueprints.enums import SpecificationImplementationStatus
 from lp.buildmaster.enums import BuildStatus
@@ -39,6 +40,7 @@
 from lp.registry.model.milestone import milestone_sort_key
 from lp.scripts.garbo import PopulateLatestPersonSourcePackageReleaseCache
 from lp.services.config import config
+from lp.services.features.testing import FeatureFixture
 from lp.services.identity.interfaces.account import AccountStatus
 from lp.services.identity.interfaces.emailaddress import IEmailAddressSet
 from lp.services.log.logger import FakeLogger
@@ -908,6 +910,15 @@
         self.assertEqual(self.view.max_results_to_display, count)
 
 
+class TestFastPersonRelatedPackagesView(TestPersonRelatedPackagesView):
+    # Re-run TestPersonRelatedPackagesView with feature flag on.
+
+    def setUp(self):
+        super(TestFastPersonRelatedPackagesView, self).setUp()
+        self.useFixture(FeatureFixture({
+            'registry.fast_related_software.enabled': 'true'}))
+
+
 class TestPersonMaintainedPackagesView(TestCaseWithFactory):
     """Test the maintained packages view."""
 

=== modified file 'lib/lp/registry/browser/tests/test_product.py'
--- lib/lp/registry/browser/tests/test_product.py	2012-11-26 21:01:54 +0000
+++ lib/lp/registry/browser/tests/test_product.py	2012-11-30 10:13:01 +0000
@@ -40,7 +40,9 @@
     IProductSet,
     License,
     )
-from lp.registry.model.product import Product
+from lp.registry.model.product import (
+    Product,
+    )
 from lp.services.config import config
 from lp.services.database.lpstorm import IStore
 from lp.services.features.testing import FeatureFixture
@@ -515,6 +517,7 @@
             # the product when the information type is changed.
             self.assertIsNotNone(updated_product.commercial_subscription)
 
+<<<<<<< TREE
     def test_change_information_type_proprietary_packaged(self):
         # It should be an error to make a Product private if it is packaged.
         self.useFixture(FeatureFixture(
@@ -549,6 +552,25 @@
                 'Some branches are neither proprietary nor embargoed.')
         self.assertThat(browser.contents, HTMLContains(tag))
 
+=======
+    def test_change_information_type_proprietary_packaged(self):
+        # It should be an error to make a Product private if it is packaged.
+        self.useFixture(FeatureFixture(
+            {u'disclosure.private_projects.enabled': u'on'}))
+        product = self.factory.makeProduct()
+        sourcepackage = self.factory.makeSourcePackage()
+        sourcepackage.setPackaging(product.development_focus, product.owner)
+        browser = self.getViewBrowser(product, '+edit', user=product.owner)
+        info_type = browser.getControl(name='field.information_type')
+        info_type.value = ['PROPRIETARY']
+        old_url = browser.url
+        browser.getControl('Change').click()
+        self.assertEqual(old_url, browser.url)
+        tag = Tag('error', 'div', text='Some series are packaged.',
+                  attrs={'class': 'message'})
+        self.assertThat(browser.contents, HTMLContains(tag))
+
+>>>>>>> MERGE-SOURCE
     def test_change_information_type_public(self):
         owner = self.factory.makePerson(name='pting')
         product = self.factory.makeProduct(

=== modified file 'lib/lp/registry/browser/tests/test_projectgroup.py'
--- lib/lp/registry/browser/tests/test_projectgroup.py	2012-11-20 20:53:20 +0000
+++ lib/lp/registry/browser/tests/test_projectgroup.py	2012-11-30 10:13:01 +0000
@@ -57,6 +57,7 @@
             team_membership_policy_data,
             cache.objects['team_membership_policy_data'])
 
+<<<<<<< TREE
     def test_proprietary_product(self):
         # Proprietary projects are not listed for people without access to
         # them.
@@ -134,6 +135,50 @@
         self.assertTrue("This is the public bug" in browser.contents)
         self.assertFalse("This is the private bug" in browser.contents)
 
+=======
+    def test_proprietary_product(self):
+        # Proprietary projects are not listed for people without access to
+        # them.
+        owner = self.factory.makePerson()
+        product = self.factory.makeProduct(
+            information_type=InformationType.PROPRIETARY,
+            project=self.project_group, owner=owner)
+        owner_browser = self.getViewBrowser(self.project_group,
+                                            user=owner)
+        with person_logged_in(owner):
+            product_name = product.name
+        self.assertIn(product_name, owner_browser.contents)
+        browser = self.getViewBrowser(self.project_group)
+        self.assertNotIn(product_name, browser.contents)
+
+    def test_proprietary_product_milestone(self):
+        # Proprietary projects are not listed for people without access to
+        # them.
+        owner = self.factory.makePerson()
+        public_product = self.factory.makeProduct(
+            information_type=InformationType.PUBLIC,
+            project=self.project_group, owner=owner)
+        public_milestone = self.factory.makeMilestone(product=public_product)
+        product = self.factory.makeProduct(
+            information_type=InformationType.PROPRIETARY,
+            project=self.project_group, owner=owner)
+        milestone = self.factory.makeMilestone(product=product,
+                                               name=public_milestone.name)
+        (group_milestone,) = self.project_group.milestones
+        self.factory.makeSpecification(milestone=public_milestone)
+        with person_logged_in(owner):
+            self.factory.makeSpecification(milestone=milestone)
+            product_name = product.displayname
+        with person_logged_in(None):
+            owner_browser = self.getViewBrowser(group_milestone, user=owner)
+            browser = self.getViewBrowser(group_milestone)
+
+        self.assertIn(product_name, owner_browser.contents)
+        self.assertIn(public_product.displayname, owner_browser.contents)
+        self.assertNotIn(product_name, browser.contents)
+        self.assertIn(public_product.displayname, browser.contents)
+
+>>>>>>> MERGE-SOURCE
 
 class TestProjectGroupEditView(TestCaseWithFactory):
     """Tests the edit view."""

=== modified file 'lib/lp/registry/configure.zcml'
=== modified file 'lib/lp/registry/doc/person.txt'
=== modified file 'lib/lp/registry/doc/product.txt'
=== modified file 'lib/lp/registry/errors.py'
=== modified file 'lib/lp/registry/interfaces/milestone.py'
--- lib/lp/registry/interfaces/milestone.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/interfaces/milestone.py	2012-11-30 10:13:01 +0000
@@ -4,6 +4,7 @@
 # pylint: disable-msg=E0211,E0213
 
 """Milestone interfaces."""
+from lp.bugs.interfaces.bugtasksearch import IBugTaskSearchBase
 
 __metaclass__ = type
 

=== modified file 'lib/lp/registry/interfaces/person.py'
=== modified file 'lib/lp/registry/interfaces/product.py'
--- lib/lp/registry/interfaces/product.py	2012-11-26 21:01:54 +0000
+++ lib/lp/registry/interfaces/product.py	2012-11-30 10:13:01 +0000
@@ -424,6 +424,7 @@
 
     id = Int(title=_('The Project ID'))
 
+<<<<<<< TREE
     def userCanView(user):
         """True if the given user has access to this product."""
 
@@ -477,6 +478,51 @@
                           "who maintains the project information in "
                           "Launchpad.")))
 
+=======
+    def userCanView(user):
+        """True if the given user has access to this product."""
+
+
+class IProductLimitedView(IHasLogo, IHasOwner, ILaunchpadUsage):
+    """Attributes that must be visible for person with artifact grants
+    on bugs, branches or specifications for the product.
+    """
+
+    displayname = exported(
+        TextLine(
+            title=_('Display Name'),
+            description=_("""The name of the project as it would appear in a
+                paragraph.""")),
+        exported_as='display_name')
+
+    logo = exported(
+        LogoImageUpload(
+            title=_("Logo"), required=False,
+            default_image_resource='/@@/product-logo',
+            description=_(
+                "An image of exactly 64x64 pixels that will be displayed in "
+                "the heading of all pages related to this project. It should "
+                "be no bigger than 50kb in size.")))
+
+    name = exported(
+        ProductNameField(
+            title=_('Name'),
+            constraint=name_validator,
+            description=_(
+                "At least one lowercase letter or number, followed by "
+                "letters, numbers, dots, hyphens or pluses. "
+                "Keep this name short; it is used in URLs as shown above.")))
+
+    owner = exported(
+        PersonChoice(
+            title=_('Maintainer'),
+            required=True,
+            vocabulary='ValidPillarOwner',
+            description=_("The restricted team, moderated team, or person "
+                          "who maintains the project information in "
+                          "Launchpad.")))
+
+>>>>>>> MERGE-SOURCE
     project = exported(
         ReferenceChoice(
             title=_('Part of'),
@@ -493,6 +539,7 @@
                 'and security policy will apply to this project.')),
         exported_as='project_group')
 
+<<<<<<< TREE
     title = exported(
         Title(
             title=_('Title'),
@@ -508,6 +555,23 @@
     IOfficialBugTagTargetPublic, IHasOOPSReferences,
     IHasRecipes, IHasCodeImports, IServiceUsage):
     """Public IProduct properties."""
+=======
+    title = exported(
+        Title(
+            title=_('Title'),
+            description=_("The project title. Should be just a few words.")))
+
+
+class IProductView(
+    ICanGetMilestonesDirectly, IHasAppointedDriver, IHasBranches,
+    IHasDrivers, IHasExternalBugTracker, IHasIcon,
+    IHasMergeProposals, IHasMilestones, IHasExpirableBugs,
+    IHasMugshot, IHasSprints, IHasTranslationImports,
+    ITranslationPolicy, IKarmaContext, IMakesAnnouncements,
+    IOfficialBugTagTargetPublic, IHasOOPSReferences,
+    ISpecificationTarget, IHasRecipes, IHasCodeImports, IServiceUsage):
+    """Public IProduct properties."""
+>>>>>>> MERGE-SOURCE
 
     registrant = exported(
         PublicPersonChoice(
@@ -529,6 +593,14 @@
                 "than having one project team that does it all."),
             required=False, vocabulary='ValidPersonOrTeam'))
 
+<<<<<<< TREE
+=======
+    drivers = Attribute(
+        "Presents the drivers of this project as a list. A list is "
+        "required because there might be a project driver and also a "
+        "driver appointed in the overarching project group.")
+
+>>>>>>> MERGE-SOURCE
     summary = exported(
         Summary(
             title=_('Summary'),
@@ -618,6 +690,19 @@
             "be displayed for all the world to see. It is NOT a wiki "
             "so you cannot undo changes."))
 
+<<<<<<< TREE
+=======
+    icon = exported(
+        IconImageUpload(
+            title=_("Icon"), required=False,
+            default_image_resource='/@@/product',
+            description=_(
+                "A small image of exactly 14x14 pixels and at most 5kb in "
+                "size, that can be used to identify this project. The icon "
+                "will be displayed next to the project name everywhere in "
+                "Launchpad that we refer to the project and link to it.")))
+
+>>>>>>> MERGE-SOURCE
     mugshot = exported(
         MugshotImageUpload(
             title=_("Brand"), required=False,
@@ -920,11 +1005,18 @@
 
 
 class IProduct(
+<<<<<<< TREE
     IBugTarget, IHasBugSupervisor, IHasDrivers, IProductEditRestricted,
     IProductModerateRestricted, IProductDriverRestricted, IProductView,
     IProductLimitedView, IProductPublic, IQuestionTarget, IRootContext,
     ISpecificationTarget, IStructuralSubscriptionTarget, IInformationType,
     IPillar):
+=======
+    IBugTarget, IHasBugSupervisor, IProductEditRestricted,
+    IProductModerateRestricted, IProductDriverRestricted, IProductView,
+    IProductLimitedView, IProductPublic, IQuestionTarget, IRootContext,
+    IStructuralSubscriptionTarget, IInformationType, IPillar):
+>>>>>>> MERGE-SOURCE
     """A Product.
 
     The Launchpad Registry describes the open source world as ProjectGroups
@@ -962,6 +1054,13 @@
         :return: An iterable of IProduct
         """
 
+    def get_users_private_products(user):
+        """Get users non-public products.
+
+        :param user: Which user are we searching products for.
+        :return: An iterable of IProduct
+        """
+
     def get_all_active(eager_load=True):
         """Get all active products.
 

=== modified file 'lib/lp/registry/javascript/sharing/tests/test_pillarsharingview.html'
=== modified file 'lib/lp/registry/model/distribution.py'
=== modified file 'lib/lp/registry/model/milestone.py'
--- lib/lp/registry/model/milestone.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/model/milestone.py	2012-11-30 10:13:01 +0000
@@ -38,10 +38,17 @@
 from zope.interface import implements
 
 from lp.app.errors import NotFoundError
+<<<<<<< TREE
 from lp.blueprints.model.specification import (
     Specification,
     visible_specification_query,
     )
+=======
+from lp.blueprints.model.specification import (
+    get_specification_privacy_filter,
+    Specification,
+    )
+>>>>>>> MERGE-SOURCE
 from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
 from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
 from lp.bugs.interfaces.bugtarget import IHasBugs
@@ -158,8 +165,13 @@
         origin, clauses = visible_specification_query(user)
         origin.extend([
             LeftJoin(Person, Specification.assigneeID == Person.id),
+<<<<<<< TREE
             ])
         milestones = self._milestone_ids_expr(user)
+=======
+            ]
+        milestones = self._milestone_ids_expr(user)
+>>>>>>> MERGE-SOURCE
 
         results = store.using(*origin).find(
             (Specification, Person),
@@ -175,8 +187,14 @@
                             SpecificationWorkItem.milestone_id.is_in(
                                 milestones),
                             SpecificationWorkItem.deleted == False)),
+<<<<<<< TREE
                     all=True)),
             *clauses)
+=======
+                    all=True)),
+            get_specification_privacy_filter(user))
+        results.config(distinct=True)
+>>>>>>> MERGE-SOURCE
         ordered_results = results.order_by(Desc(Specification.priority),
                                            Specification.definition_status,
                                            Specification.implementation_status,

=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py	2012-11-28 05:21:33 +0000
+++ lib/lp/registry/model/person.py	2012-11-30 10:13:01 +0000
@@ -130,11 +130,20 @@
     SpecificationSort,
     )
 from lp.blueprints.model.specification import (
-    get_specification_filters,
+<<<<<<< TREE
+    get_specification_filters,
+=======
+    get_specification_filters,
+    get_specification_privacy_filter,
+>>>>>>> MERGE-SOURCE
     HasSpecificationsMixin,
     spec_started_clause,
     Specification,
+<<<<<<< TREE
     visible_specification_query,
+=======
+    spec_started_clause,
+>>>>>>> MERGE-SOURCE
     )
 from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
 from lp.bugs.interfaces.bugtarget import IBugTarget
@@ -206,10 +215,18 @@
     )
 from lp.registry.interfaces.personnotification import IPersonNotificationSet
 from lp.registry.interfaces.persontransferjob import IPersonMergeJobSource
-from lp.registry.interfaces.product import (
-    IProduct,
-    IProductSet,
-    )
+<<<<<<< TREE
+from lp.registry.interfaces.product import (
+    IProduct,
+    IProductSet,
+    )
+=======
+from lp.registry.interfaces.pillar import IPillarNameSet
+from lp.registry.interfaces.product import (
+    IProduct,
+    IProductSet,
+    )
+>>>>>>> MERGE-SOURCE
 from lp.registry.interfaces.projectgroup import IProjectGroup
 from lp.registry.interfaces.role import IPersonRoles
 from lp.registry.interfaces.ssh import (
@@ -264,6 +281,7 @@
     SQLBase,
     sqlvalues,
     )
+from lp.services.features import getFeatureFlag
 from lp.services.helpers import (
     ensure_unicode,
     shortlist,
@@ -327,6 +345,7 @@
     Archive,
     validate_ppa,
     )
+from lp.soyuz.model.reporting import LatestPersonSourcePackageReleaseCache
 from lp.soyuz.model.publishing import SourcePackagePublishingHistory
 from lp.soyuz.model.reporting import LatestPersonSourcePackageReleaseCache
 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
@@ -877,6 +896,7 @@
         if SpecificationFilter.APPROVER in filter:
             role_clauses.append(Specification.approver == self)
         if SpecificationFilter.SUBSCRIBER in filter:
+<<<<<<< TREE
             role_clauses.append(
                 Specification.id.is_in(
                     Select(SpecificationSubscription.specificationID,
@@ -905,6 +925,34 @@
         results.config(distinct=True)
         if quantity is not None:
             results = results[:quantity]
+=======
+            role_clauses.append(
+                Specification.id.is_in(
+                    Select(SpecificationSubscription.specificationID,
+                        [SpecificationSubscription.person == self]
+                    )))
+        clauses = [Or(*role_clauses), get_specification_privacy_filter(user)]
+        # Defaults for completeness: if nothing is said about completeness
+        # then we want to show INCOMPLETE.
+        if SpecificationFilter.COMPLETE not in filter:
+            if (in_progress and SpecificationFilter.INCOMPLETE not in filter
+                and SpecificationFilter.ALL not in filter):
+                clauses.append(spec_started_clause)
+            filter.add(SpecificationFilter.INCOMPLETE)
+
+        clauses.extend(get_specification_filters(filter))
+        results = Store.of(self).find(Specification, *clauses)
+        # The default sort is priority descending, so only explictly sort for
+        # DATE.
+        if sort == SpecificationSort.DATE:
+            sort = Desc(Specification.datecreated)
+        elif getattr(sort, 'enum', None) is SpecificationSort:
+            sort = None
+        if sort is not None:
+            results = results.order_by(sort)
+        if quantity is not None:
+            results = results[:quantity]
+>>>>>>> MERGE-SOURCE
         return results
 
     # XXX: Tom Berger 2008-04-14 bug=191799:
@@ -1036,6 +1084,7 @@
         # We want this person's total karma on a given context (that is,
         # across all different categories) here; that's why we use a
         # "KarmaCache.category IS NULL" clause here.
+<<<<<<< TREE
         from lp.registry.model.product import (
             Product,
             ProductSet,
@@ -1108,6 +1157,77 @@
         return query
 
     def getAffiliatedPillars(self, user):
+=======
+        query = """
+            SELECT PillarName.name, KarmaCache.karmavalue
+            FROM KarmaCache
+            JOIN PillarName ON
+                COALESCE(KarmaCache.distribution, -1) =
+                COALESCE(PillarName.distribution, -1)
+                AND
+                COALESCE(KarmaCache.product, -1) =
+                COALESCE(PillarName.product, -1)
+            WHERE person = %(person)s
+                AND KarmaCache.category IS NULL
+                AND KarmaCache.project IS NULL
+            ORDER BY karmavalue DESC, name
+            LIMIT %(limit)s;
+            """ % sqlvalues(person=self, limit=limit)
+        cur = cursor()
+        cur.execute(query)
+        return cur.fetchall()
+
+    def _genAffiliatedProductSql(self, user=None):
+        """Helper to generate the product sql for getAffiliatePillars"""
+        base_query = """
+            SELECT name, 3 as kind, displayname
+            FROM product p
+            WHERE
+                p.active = True
+                AND (
+                    p.driver = %(person)s
+                    OR p.owner = %(person)s
+                    OR p.bug_supervisor = %(person)s
+                )
+        """ % sqlvalues(person=self)
+
+        if user is not None:
+            roles = IPersonRoles(user)
+            if roles.in_admin or roles.in_commercial_admin:
+                return base_query
+
+        # This is the raw sql version of model/product getProductPrivacyFilter
+        granted_products = """
+            SELECT p.id
+            FROM product p,
+                 accesspolicygrantflat apflat,
+                 teamparticipation part,
+                 accesspolicy ap
+             WHERE
+                apflat.grantee = part.team
+                AND part.person = %(user)s
+                AND apflat.policy = ap.id
+                AND ap.product = p.id
+                AND ap.type = p.information_type
+        """ % sqlvalues(user=user)
+
+        # We have to generate the sqlvalues first so that they're properly
+        # setup and escaped. Then we combine the above query which is already
+        # processed.
+        query_values = sqlvalues(information_type=InformationType.PUBLIC)
+        query_values.update(granted_sql=granted_products)
+
+        query = base_query + """
+                AND (
+                    p.information_type = %(information_type)s
+                    OR p.information_type is NULL
+                    OR p.id IN (%(granted_sql)s)
+                )
+        """ % query_values
+        return query
+
+    def getAffiliatedPillars(self, user):
+>>>>>>> MERGE-SOURCE
         """See `IPerson`."""
         find_spec = (PillarName, SQL('kind'), SQL('displayname'))
         base = """PillarName
@@ -1495,7 +1615,12 @@
                           Specification.milestoneID) == Milestone.id),
             ])
         today = datetime.today().date()
+<<<<<<< TREE
         query.extend([
+=======
+        query = And(
+            get_specification_privacy_filter(user),
+>>>>>>> MERGE-SOURCE
             Milestone.dateexpected <= date, Milestone.dateexpected >= today,
             WorkItem.deleted == False,
             OR(WorkItem.assignee_id.is_in(self.participant_ids),
@@ -2834,6 +2959,7 @@
 
     def getLatestUploadedPPAPackages(self):
         """See `IPerson`."""
+<<<<<<< TREE
         return self._latestReleasesQuery(uploader_only=True, ppa_only=True)
 
     def _releasesQueryFilter(self, uploader_only=False, ppa_only=False):
@@ -2978,6 +3104,246 @@
             bulk.load_related(Archive, rows, ['archiveID'])
 
         return DecoratedResultSet(rs, pre_iter_hook=load_related_objects)
+=======
+        return self._latestReleasesQuery(uploader_only=True, ppa_only=True)
+
+    def _releasesQueryFilter(self, uploader_only=False, ppa_only=False):
+        """Return the filter used to find latest published source package
+        releases (SPRs) related to this person.
+
+        :param uploader_only: controls if we are interested in SPRs where
+            the person in question is only the uploader (creator) and not the
+            maintainer (debian-syncs) if the `ppa_only` parameter is also
+            False, or, if the flag is False, it returns all SPR maintained
+            by this person.
+
+        :param ppa_only: controls if we are interested only in source
+            package releases targeted to any PPAs or, if False, sources
+            targeted to primary archives.
+
+        Active 'ppa_only' flag is usually associated with active
+        'uploader_only' because there shouldn't be any sense of maintainership
+        for packages uploaded to PPAs by someone else than the user himself.
+        """
+        clauses = []
+        if uploader_only:
+            clauses.append(
+                LatestPersonSourcePackageReleaseCache.creator_id == self.id)
+        if ppa_only:
+            # Source maintainer is irrelevant for PPA uploads.
+            pass
+        elif uploader_only:
+            lpspr = ClassAlias(LatestPersonSourcePackageReleaseCache, 'lpspr')
+            clauses.append(Not(Exists(Select(1,
+            where=And(
+                lpspr.sourcepackagename_id ==
+                    LatestPersonSourcePackageReleaseCache.sourcepackagename_id,
+                lpspr.upload_archive_id ==
+                    LatestPersonSourcePackageReleaseCache.upload_archive_id,
+                lpspr.upload_distroseries_id ==
+                    LatestPersonSourcePackageReleaseCache.upload_distroseries_id,
+                lpspr.archive_purpose != ArchivePurpose.PPA,
+                lpspr.maintainer_id == self.id),
+            tables=lpspr))))
+        else:
+            clauses.append(
+                LatestPersonSourcePackageReleaseCache.maintainer_id == self.id)
+        if ppa_only:
+            clauses.append(
+                LatestPersonSourcePackageReleaseCache.archive_purpose ==
+                ArchivePurpose.PPA)
+        else:
+            clauses.append(
+                LatestPersonSourcePackageReleaseCache.archive_purpose !=
+                ArchivePurpose.PPA)
+        return clauses
+
+    def _hasReleasesQuery(self, uploader_only=False, ppa_only=False):
+        """Are there sourcepackagereleases (SPRs) related to this person.
+        See `_releasesQueryFilter` for details on the criteria used.
+        """
+        if not getFeatureFlag('registry.fast_related_software.enabled'):
+            return self._legacy_hasReleasesQuery(uploader_only, ppa_only)
+
+        clauses = self._releasesQueryFilter(uploader_only, ppa_only)
+        rs = Store.of(self).using(LatestPersonSourcePackageReleaseCache).find(
+            LatestPersonSourcePackageReleaseCache.publication_id, *clauses)
+        return not rs.is_empty()
+
+    def _latestReleasesQuery(self, uploader_only=False, ppa_only=False):
+        """Return the sourcepackagereleases records related to this person.
+        See `_releasesQueryFilter` for details on the criteria used."""
+
+        if not getFeatureFlag('registry.fast_related_software.enabled'):
+            return self._legacy_latestReleasesQuery(uploader_only, ppa_only)
+
+        clauses = self._releasesQueryFilter(uploader_only, ppa_only)
+        rs = Store.of(self).find(
+            LatestPersonSourcePackageReleaseCache, *clauses).order_by(
+            Desc(LatestPersonSourcePackageReleaseCache.dateuploaded))
+
+        def load_related_objects(rows):
+            if rows and rows[0].maintainer_id:
+                list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+                    set(map(attrgetter("maintainer_id"), rows))))
+            bulk.load_related(
+                SourcePackageName, rows, ['sourcepackagename_id'])
+            bulk.load_related(
+                SourcePackageRelease, rows, ['sourcepackagerelease_id'])
+            bulk.load_related(Archive, rows, ['upload_archive_id'])
+
+        return DecoratedResultSet(rs, pre_iter_hook=load_related_objects)
+
+    def _legacy_releasesQueryFilter(self, uploader_only=False, ppa_only=False):
+        """Return the filter used to find sourcepackagereleases (SPRs)
+        related to this person.
+
+        :param uploader_only: controls if we are interested in SPRs where
+            the person in question is only the uploader (creator) and not the
+            maintainer (debian-syncs) if the `ppa_only` parameter is also
+            False, or, if the flag is False, it returns all SPR maintained
+            by this person.
+
+        :param ppa_only: controls if we are interested only in source
+            package releases targeted to any PPAs or, if False, sources
+            targeted to primary archives.
+
+        Active 'ppa_only' flag is usually associated with active
+        'uploader_only' because there shouldn't be any sense of maintainership
+        for packages uploaded to PPAs by someone else than the user himself.
+        """
+        clauses = [SourcePackageRelease.upload_archive == Archive.id]
+
+        if uploader_only:
+            clauses.append(SourcePackageRelease.creator == self)
+
+        if ppa_only:
+            # Source maintainer is irrelevant for PPA uploads.
+            pass
+        elif uploader_only:
+            clauses.append(SourcePackageRelease.maintainer != self)
+        else:
+            clauses.append(SourcePackageRelease.maintainer == self)
+
+        if ppa_only:
+            clauses.append(Archive.purpose == ArchivePurpose.PPA)
+        else:
+            clauses.append(Archive.purpose != ArchivePurpose.PPA)
+
+        return clauses
+
+    def _legacy_hasReleasesQuery(self, uploader_only=False, ppa_only=False):
+        """Are there sourcepackagereleases (SPRs) related to this person.
+        See `_legacy_releasesQueryFilter` for details on the criteria used.
+        """
+        clauses = self._legacy_releasesQueryFilter(uploader_only, ppa_only)
+        spph = ClassAlias(SourcePackagePublishingHistory, "spph")
+        tables = (
+            SourcePackageRelease,
+            Join(
+                spph, spph.sourcepackagereleaseID == SourcePackageRelease.id),
+            Join(Archive, Archive.id == spph.archiveID))
+        rs = Store.of(self).using(*tables).find(
+            SourcePackageRelease.id, clauses)
+        return not rs.is_empty()
+
+    def _legacy_latestReleasesQuery(self, uploader_only=False, ppa_only=False):
+        """Return the sourcepackagereleases (SPRs) related to this person.
+        See `_legacy_releasesQueryFilter` for details on the criteria used."""
+        clauses = self._legacy_releasesQueryFilter(uploader_only, ppa_only)
+        spph = ClassAlias(SourcePackagePublishingHistory, "spph")
+        rs = Store.of(self).find(
+            SourcePackageRelease,
+            SourcePackageRelease.id.is_in(
+                Select(
+                    SourcePackageRelease.id,
+                    tables=[
+                        SourcePackageRelease,
+                        Join(
+                            spph,
+                            spph.sourcepackagereleaseID ==
+                            SourcePackageRelease.id),
+                        Join(Archive, Archive.id == spph.archiveID)],
+                    where=And(*clauses),
+                    order_by=[SourcePackageRelease.upload_distroseriesID,
+                              SourcePackageRelease.sourcepackagenameID,
+                              SourcePackageRelease.upload_archiveID,
+                              Desc(SourcePackageRelease.dateuploaded)],
+                    distinct=(
+                        SourcePackageRelease.upload_distroseriesID,
+                        SourcePackageRelease.sourcepackagenameID,
+                        SourcePackageRelease.upload_archiveID)))
+        ).order_by(
+            Desc(SourcePackageRelease.dateuploaded), SourcePackageRelease.id)
+
+        def load_related_objects(rows):
+            list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+                set(map(attrgetter("maintainerID"), rows))))
+            bulk.load_related(SourcePackageName, rows, ['sourcepackagenameID'])
+            bulk.load_related(Archive, rows, ['upload_archiveID'])
+
+        return DecoratedResultSet(rs, pre_iter_hook=load_related_objects)
+
+    def hasSynchronisedPublishings(self):
+        """See `IPerson`."""
+        spph = ClassAlias(SourcePackagePublishingHistory, "spph")
+        ancestor_spph = ClassAlias(
+            SourcePackagePublishingHistory, "ancestor_spph")
+        tables = (
+            SourcePackageRelease,
+            Join(
+                spph,
+                spph.sourcepackagereleaseID ==
+                SourcePackageRelease.id),
+            Join(Archive, Archive.id == spph.archiveID),
+            Join(ancestor_spph, ancestor_spph.id == spph.ancestorID))
+        rs = Store.of(self).using(*tables).find(
+            spph.id,
+            spph.creatorID == self.id,
+            ancestor_spph.archiveID != spph.archiveID,
+            Archive.purpose == ArchivePurpose.PRIMARY)
+        return not rs.is_empty()
+
+    def getLatestSynchronisedPublishings(self):
+        """See `IPerson`."""
+        spph = ClassAlias(SourcePackagePublishingHistory, "spph")
+        ancestor_spph = ClassAlias(
+            SourcePackagePublishingHistory, "ancestor_spph")
+        rs = Store.of(self).find(
+            SourcePackagePublishingHistory,
+            SourcePackagePublishingHistory.id.is_in(
+                Select(
+                    spph.id,
+                    tables=[
+                        SourcePackageRelease,
+                        Join(
+                            spph, spph.sourcepackagereleaseID ==
+                            SourcePackageRelease.id),
+                        Join(Archive, Archive.id == spph.archiveID),
+                        Join(
+                            ancestor_spph,
+                            ancestor_spph.id == spph.ancestorID)],
+                    where=And(
+                        spph.creatorID == self.id,
+                        ancestor_spph.archiveID != spph.archiveID,
+                        Archive.purpose == ArchivePurpose.PRIMARY),
+                    order_by=[spph.distroseriesID,
+                              SourcePackageRelease.sourcepackagenameID,
+                              Desc(spph.datecreated), Desc(spph.id)],
+                    distinct=(
+                        spph.distroseriesID,
+                        SourcePackageRelease.sourcepackagenameID)
+                    ))).order_by(
+            Desc(SourcePackagePublishingHistory.datecreated),
+            Desc(SourcePackagePublishingHistory.id))
+
+        def load_related_objects(rows):
+            bulk.load_related(
+                SourcePackageRelease, rows, ['sourcepackagereleaseID'])
+            bulk.load_related(Archive, rows, ['archiveID'])
+
+        return DecoratedResultSet(rs, pre_iter_hook=load_related_objects)
+>>>>>>> MERGE-SOURCE
 
     def createRecipe(self, name, description, recipe_text, distroseries,
                      registrant, daily_build_archive=None, build_daily=False):

=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py	2012-11-27 13:43:33 +0000
+++ lib/lp/registry/model/product.py	2012-11-30 10:13:01 +0000
@@ -54,6 +54,10 @@
     )
 from zope.security.proxy import removeSecurityProxy
 
+from lp.registry.model.accesspolicy import (
+    AccessPolicy,
+    AccessPolicyGrantFlat,
+    )
 from lp.answers.enums import QUESTION_STATUS_DEFAULT_SEARCH
 from lp.answers.interfaces.faqtarget import IFAQTarget
 from lp.answers.model.faq import (
@@ -89,9 +93,20 @@
     )
 from lp.app.interfaces.services import IService
 from lp.app.model.launchpad import InformationTypeMixin
+<<<<<<< TREE
 from lp.blueprints.enums import SpecificationFilter
+=======
+from lp.blueprints.enums import (
+    SpecificationFilter,
+    )
+>>>>>>> MERGE-SOURCE
 from lp.blueprints.model.specification import (
-    get_specification_filters,
+<<<<<<< TREE
+    get_specification_filters,
+=======
+    get_specification_filters,
+    get_specification_privacy_filter,
+>>>>>>> MERGE-SOURCE
     HasSpecificationsMixin,
     Specification,
     SPECIFICATION_POLICY_ALLOWED_TYPES,
@@ -137,8 +152,12 @@
 from lp.registry.errors import (
     CannotChangeInformationType,
     CommercialSubscribersOnly,
+<<<<<<< TREE
     ProprietaryProduct,
     VoucherAlreadyRedeemed,
+=======
+    VoucherAlreadyRedeemed,
+>>>>>>> MERGE-SOURCE
     )
 from lp.registry.interfaces.accesspolicy import (
     IAccessPolicyArtifactSource,
@@ -161,11 +180,15 @@
     LicenseStatus,
     )
 from lp.registry.interfaces.productrelease import IProductReleaseSet
+<<<<<<< TREE
 from lp.registry.interfaces.role import IPersonRoles
 from lp.registry.model.accesspolicy import (
     AccessPolicy,
     AccessPolicyGrantFlat,
     )
+=======
+from lp.registry.interfaces.role import IPersonRoles
+>>>>>>> MERGE-SOURCE
 from lp.registry.model.announcement import MakesAnnouncements
 from lp.registry.model.commercialsubscription import CommercialSubscription
 from lp.registry.model.distribution import Distribution
@@ -183,6 +206,7 @@
 from lp.registry.model.productlicense import ProductLicense
 from lp.registry.model.productrelease import ProductRelease
 from lp.registry.model.productseries import ProductSeries
+from lp.registry.model.teammembership import TeamParticipation
 from lp.registry.model.series import ACTIVE_STATUSES
 from lp.registry.model.sourcepackagename import SourcePackageName
 from lp.registry.model.teammembership import TeamParticipation
@@ -524,6 +548,7 @@
 
         # If you have a commercial subscription, but it's not current, you
         # cannot set the information type to a PROPRIETARY type.
+<<<<<<< TREE
         if not self.has_current_commercial_subscription:
             yield CommercialSubscribersOnly(
                 'A valid commercial subscription is required for private'
@@ -532,6 +557,20 @@
             self.bug_supervisor.membership_policy in INCLUSIVE_TEAM_POLICY):
             yield CannotChangeInformationType(
                 'Bug supervisor has inclusive membership.')
+=======
+        if not self._SO_creating and value in PROPRIETARY_INFORMATION_TYPES:
+            if not self.packagings.is_empty():
+                raise CannotChangeInformationType('Some series are packaged.')
+            # Create the complimentary commercial subscription for the product.
+            self._ensure_complimentary_subscription()
+
+            if not self.has_current_commercial_subscription:
+                raise CommercialSubscribersOnly(
+                    'A valid commercial subscription is required for private'
+                    ' Projects.')
+
+        return value
+>>>>>>> MERGE-SOURCE
 
     _information_type = EnumCol(
         enum=InformationType, default=InformationType.PUBLIC,
@@ -1458,15 +1497,30 @@
         #  - completeness.
         #  - informational.
         #
+<<<<<<< TREE
         tables, clauses = visible_specification_query(user)
         clauses.append(Specification.product == self)
         clauses.extend(get_specification_filters(filter))
+=======
+        clauses = [Specification.product == self,
+                   get_specification_privacy_filter(user)]
+        clauses.extend(get_specification_filters(filter))
+>>>>>>> MERGE-SOURCE
         if prejoin_people:
+<<<<<<< TREE
             results = self._preload_specifications_people(tables, clauses)
+=======
+            results = self._preload_specifications_people(clauses)
+>>>>>>> MERGE-SOURCE
         else:
+<<<<<<< TREE
             tableset = Store.of(self).using(*tables)
             results = tableset.find(Specification, *clauses)
         results.order_by(order).config(distinct=True)
+=======
+            results = Store.of(self).find(Specification, *clauses)
+        results.order_by(order)
+>>>>>>> MERGE-SOURCE
         if quantity is not None:
             results = results[:quantity]
         return results
@@ -1786,6 +1840,7 @@
     def people(self):
         return getUtility(IPersonSet)
 
+<<<<<<< TREE
     @classmethod
     def latest(cls, user, quantity=5):
         """See `IProductSet`."""
@@ -1824,6 +1879,48 @@
         clause = cls.getProductPrivacyFilter(user)
         result = IStore(Product).find(Product, Product.active,
                     clause).order_by(Desc(Product.datecreated))
+=======
+    def latest(self, quantity=5):
+        if quantity is None:
+            return self.all_active
+        else:
+            return self.all_active[:quantity]
+
+    @property
+    def all_active(self):
+        return self.get_all_active(None)
+
+    @staticmethod
+    def getProductPrivacyFilter(user):
+        if user is not None:
+            roles = IPersonRoles(user)
+            if roles.in_admin or roles.in_commercial_admin:
+                return True
+        granted_products = And(
+            AccessPolicyGrantFlat.grantee_id == TeamParticipation.teamID,
+            TeamParticipation.person == user,
+            AccessPolicyGrantFlat.policy == AccessPolicy.id,
+            AccessPolicy.product == Product.id,
+            AccessPolicy.type == Product._information_type)
+        return Or(Product._information_type == InformationType.PUBLIC,
+                  Product._information_type == None,
+                  Product.id.is_in(Select(Product.id, granted_products)))
+
+    @classmethod
+    def get_users_private_products(cls, user):
+        """List the non-public products the user owns."""
+        result = IStore(Product).find(
+            Product,
+            Product._owner == user,
+            Product._information_type.is_in(PROPRIETARY_INFORMATION_TYPES))
+        return result
+
+    @classmethod
+    def get_all_active(cls, user, eager_load=True):
+        clause = cls.getProductPrivacyFilter(user)
+        result = IStore(Product).find(Product, Product.active,
+                    clause).order_by(Desc(Product.datecreated))
+>>>>>>> MERGE-SOURCE
         if not eager_load:
             return result
 

=== modified file 'lib/lp/registry/model/projectgroup.py'
--- lib/lp/registry/model/projectgroup.py	2012-11-20 20:52:40 +0000
+++ lib/lp/registry/model/projectgroup.py	2012-11-30 10:13:01 +0000
@@ -433,8 +433,13 @@
         privacy_filter = ProductSet.getProductPrivacyFilter(user)
         conditions = And(Milestone.product == Product.id,
                          Product.project == self,
+<<<<<<< TREE
                          Product.active == True,
                          privacy_filter)
+=======
+                         Product.active == True,
+                         ProductSet.getProductPrivacyFilter(user))
+>>>>>>> MERGE-SOURCE
         result = store.find(columns, conditions)
         result.group_by(Milestone.name)
         if only_active:
@@ -444,6 +449,7 @@
         result.order_by(
             'milestone_sort_key(MIN(Milestone.dateexpected), Milestone.name) '
             'DESC')
+<<<<<<< TREE
         # An extra query is required here in order to get the correct
         # products without affecting the group/order of the query above.
         products_by_name = {}
@@ -459,6 +465,22 @@
                 store.find((Product, Milestone.name), product_conditions)):
                 if name not in products_by_name.keys():
                     products_by_name[name] = product
+=======
+        # An extra query is required here in order to get the correct
+        # products without affecting the group/order of the query above.
+        products_by_name = {}
+        if result.any() is not None:
+            milestone_names = [data[0] for data in result]
+            product_conditions = And(
+                Product.project == self,
+                Milestone.product == Product.id,
+                Product.active == True,
+                In(Milestone.name, milestone_names))
+            for product, name in (
+                store.find((Product, Milestone.name), product_conditions)):
+                if name not in products_by_name.keys():
+                    products_by_name[name] = product
+>>>>>>> MERGE-SOURCE
         return shortlist(
             [ProjectMilestone(
                 self, name, dateexpected, active,

=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/model/sourcepackage.py	2012-11-30 10:13:01 +0000
@@ -54,10 +54,14 @@
     ISourcePackageFactory,
     )
 from lp.registry.model.hasdrivers import HasDriversMixin
+<<<<<<< TREE
 from lp.registry.model.packaging import (
     Packaging,
     PackagingUtil,
     )
+=======
+from lp.registry.model.packaging import Packaging, PackagingUtil
+>>>>>>> MERGE-SOURCE
 from lp.registry.model.suitesourcepackage import SuiteSourcePackage
 from lp.services.database.lpstorm import IStore
 from lp.services.database.sqlbase import (

=== modified file 'lib/lp/registry/scripts/productreleasefinder/finder.py'
--- lib/lp/registry/scripts/productreleasefinder/finder.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/scripts/productreleasefinder/finder.py	2012-11-30 10:13:01 +0000
@@ -114,6 +114,7 @@
         Returns a list of (product_name, filters) for each product in
         the database, where the filter keys are series names.
         """
+<<<<<<< TREE
         found_globs = IStore(Product).find(
             (Product.name, ProductSeries.name, ProductSeries.releasefileglob),
             Product.id == ProductSeries.productID,
@@ -132,6 +133,33 @@
             filter_pattern = FilterPattern(series_name, glob)
             products_with_filters[product_name].append(filter_pattern)
         return products_with_filters.items()
+=======
+        todo = []
+
+        self.ztm.begin()
+        products = getUtility(IProductSet)
+        for product in products.get_all_active(None, eager_load=False):
+            filters = []
+
+            for series in product.series:
+                if (series.status == SeriesStatus.OBSOLETE
+                    or not series.releasefileglob):
+                    continue
+
+                filters.append(FilterPattern(series.name,
+                                             series.releasefileglob))
+
+            if not len(filters):
+                continue
+
+            self.log.info("%s has %d series with information", product.name,
+                             len(filters))
+
+            todo.append((product.name, filters))
+        self.ztm.abort()
+
+        return todo
+>>>>>>> MERGE-SOURCE
 
     def handleProduct(self, product_name, filters):
         """Scan for tarballs and create ProductReleases for the given product.

=== modified file 'lib/lp/registry/tests/test_adapters.py'
--- lib/lp/registry/tests/test_adapters.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/tests/test_adapters.py	2012-11-30 10:13:01 +0000
@@ -11,6 +11,7 @@
     package_to_sourcepackagename,
     productseries_to_product,
     sourcepackage_to_distribution,
+    information_type_from_product,
     )
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.product import IProduct

=== modified file 'lib/lp/registry/tests/test_milestone.py'
--- lib/lp/registry/tests/test_milestone.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/tests/test_milestone.py	2012-11-30 10:13:01 +0000
@@ -6,6 +6,7 @@
 __metaclass__ = type
 
 from operator import attrgetter
+from storm.exceptions import NoneError
 import unittest
 
 from storm.exceptions import NoneError
@@ -474,6 +475,7 @@
         spec, target_milestone = self._create_milestones_on_target(
             product=product)
         milestone = projectgroup.getMilestone(name=target_milestone.name)
+<<<<<<< TREE
         self.assertContentEqual([spec], milestone.getSpecifications(None))
 
     def makeMixedMilestone(self):
@@ -519,6 +521,70 @@
             specification, list(milestone.getSpecifications(owner)))
         self.assertNotIn(
             specification, list(milestone.getSpecifications(None)))
+=======
+        self.assertContentEqual([spec], milestone.getSpecifications(None))
+
+    def makeMixedMilestone(self):
+        projectgroup = self.factory.makeProject()
+        owner = self.factory.makePerson()
+        public_product = self.factory.makeProduct(project=projectgroup)
+        public_milestone = self.factory.makeMilestone(product=public_product)
+        product = self.factory.makeProduct(
+            owner=owner, information_type=InformationType.PROPRIETARY,
+            project=projectgroup, bug_sharing_policy=BugSharingPolicy.PUBLIC)
+        target_milestone = self.factory.makeMilestone(
+            product=product, name=public_milestone.name)
+        milestone = projectgroup.getMilestone(name=public_milestone.name)
+        return milestone, target_milestone, owner
+
+    def test_getSpecifications_milestone_privacy(self):
+        # Ensure getSpecifications respects milestone privacy.
+        # This looks wrong, because the specification is actually public, and
+        # we don't normally hide specifications based on the visibility of
+        # their products.  But we're not trying to hide the specification.
+        # We're hiding the fact that this specification is associated with
+        # a proprietary Product milestone.  We create a proprietary product
+        # because that's the only way to get a proprietary milestone.
+        milestone, target_milestone, owner = self.makeMixedMilestone()
+        with person_logged_in(owner):
+            spec = self.factory.makeSpecification(milestone=target_milestone)
+        self.assertContentEqual([],
+                                milestone.getSpecifications(None))
+        self.assertContentEqual([spec],
+                                milestone.getSpecifications(owner))
+
+    def test_getSpecifications_specification_privacy(self):
+        # Only specifications visible to the specified user are listed.
+        owner = self.factory.makePerson()
+        enum = SpecificationSharingPolicy
+        product = self.factory.makeProduct(
+            owner=owner, specification_sharing_policy=enum.PROPRIETARY)
+        milestone = self.factory.makeMilestone(product=product)
+        specification = self.factory.makeSpecification(
+            information_type=InformationType.PROPRIETARY,
+            milestone=milestone)
+        self.assertIn(
+            specification, list(milestone.getSpecifications(owner)))
+        self.assertNotIn(
+            specification, list(milestone.getSpecifications(None)))
+
+    def test_bugtasks_milestone_privacy(self):
+        # Ensure bugtasks respects milestone privacy.
+        # This looks wrong, because the bugtask is actually public, and we
+        # don't normally hide bugtasks based on the visibility of their
+        # products.  But we're not trying to hide the bugtask.  We're hiding
+        # the fact that this bugtask is associated with a proprietary Product
+        # milestone.  We create a proprietary product because that's the only
+        # way to get a proprietary milestone.
+        milestone, target_milestone, owner = self.makeMixedMilestone()
+        with person_logged_in(owner):
+            bugtask = self.factory.makeBugTask(
+                target=target_milestone.product)
+        with person_logged_in(bugtask.owner):
+            bugtask.transitionToMilestone(target_milestone, owner)
+        self.assertContentEqual([], milestone.bugtasks(None))
+        self.assertContentEqual([bugtask], milestone.bugtasks(owner))
+>>>>>>> MERGE-SOURCE
 
     def test_milestones_with_deleted_workitems(self):
         # Deleted work items do not cause the specification to show up

=== modified file 'lib/lp/registry/tests/test_milestone_vocabularies.py'
--- lib/lp/registry/tests/test_milestone_vocabularies.py	2012-11-16 14:07:39 +0000
+++ lib/lp/registry/tests/test_milestone_vocabularies.py	2012-11-30 10:13:01 +0000
@@ -23,9 +23,14 @@
 from lp.testing.layers import DatabaseFunctionalLayer
 
 
+<<<<<<< TREE
 class TestMilestoneVocabulary(TestCaseWithFactory):
     """Test that the MilestoneVocabulary behaves as expected."""
 
+=======
+class TestMilestoneVocabulary(TestCase):
+    """Test that the MilestoneVocabulary behaves as expected."""
+>>>>>>> MERGE-SOURCE
     layer = DatabaseFunctionalLayer
 
     def setUp(self):

=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/tests/test_person.py	2012-11-30 10:13:01 +0000
@@ -13,6 +13,7 @@
 import pytz
 from storm.locals import Desc
 from storm.store import Store
+from storm.locals import Desc
 from testtools.matchers import (
     Equals,
     LessThan,
@@ -25,14 +26,25 @@
 from lp.answers.model.answercontact import AnswerContact
 from lp.app.enums import InformationType
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
-from lp.blueprints.enums import (
-    NewSpecificationDefinitionStatus,
-    SpecificationDefinitionStatus,
-    SpecificationFilter,
-    SpecificationImplementationStatus,
-    SpecificationPriority,
-    SpecificationSort,
-    )
+<<<<<<< TREE
+from lp.blueprints.enums import (
+    NewSpecificationDefinitionStatus,
+    SpecificationDefinitionStatus,
+    SpecificationFilter,
+    SpecificationImplementationStatus,
+    SpecificationPriority,
+    SpecificationSort,
+    )
+=======
+from lp.blueprints.enums import (
+    NewSpecificationDefinitionStatus,
+    SpecificationDefinitionStatus,
+    SpecificationImplementationStatus,
+    SpecificationPriority,
+    SpecificationFilter,
+    SpecificationSort,
+    )
+>>>>>>> MERGE-SOURCE
 from lp.blueprints.model.specification import Specification
 from lp.bugs.interfaces.bugtasksearch import (
     get_person_bugtasks_search_params,
@@ -745,26 +757,49 @@
         self.bzr = product_set.getByName('bzr')
         self.now = datetime.now(pytz.UTC)
 
-    def test_canDeactivateAccount_private_projects(self):
-        """A user owning non-public products cannot be deactivated."""
-        user = self.factory.makePerson()
-        self.factory.makeProduct(
-            information_type=InformationType.PUBLIC,
-            name="public",
-            owner=user)
-        self.factory.makeProduct(
-            information_type=InformationType.PROPRIETARY,
-            name="private",
-            owner=user)
-
-        login(user.preferredemail.email)
-        can_deactivate, errors = user.canDeactivateAccountWithErrors()
-
-        self.assertFalse(can_deactivate)
-        expected_error = ('This account cannot be deactivated because it owns '
-                        'the following non-public products: private')
-        self.assertIn(expected_error, errors)
-
+<<<<<<< TREE
+    def test_canDeactivateAccount_private_projects(self):
+        """A user owning non-public products cannot be deactivated."""
+        user = self.factory.makePerson()
+        self.factory.makeProduct(
+            information_type=InformationType.PUBLIC,
+            name="public",
+            owner=user)
+        self.factory.makeProduct(
+            information_type=InformationType.PROPRIETARY,
+            name="private",
+            owner=user)
+
+        login(user.preferredemail.email)
+        can_deactivate, errors = user.canDeactivateAccountWithErrors()
+
+        self.assertFalse(can_deactivate)
+        expected_error = ('This account cannot be deactivated because it owns '
+                        'the following non-public products: private')
+        self.assertIn(expected_error, errors)
+
+=======
+    def test_canDeactivateAccount_private_projects(self):
+        """A user owning non-public products cannot be deactivated."""
+        user = self.factory.makePerson()
+        public_product = self.factory.makeProduct(
+            information_type=InformationType.PUBLIC,
+            name="public",
+            owner=user)
+        public_product = self.factory.makeProduct(
+            information_type=InformationType.PROPRIETARY,
+            name="private",
+            owner=user)
+
+        login(user.preferredemail.email)
+        can_deactivate, errors = user.canDeactivateAccountWithErrors()
+
+        self.assertFalse(can_deactivate)
+        expected_error = ('This account cannot be deactivated because it owns '
+                        'the following non-public products: private')
+        self.assertIn(expected_error, errors)
+
+>>>>>>> MERGE-SOURCE
     def test_deactivateAccount_copes_with_names_already_in_use(self):
         """When a user deactivates his account, its name is changed.
 

=== modified file 'lib/lp/registry/tests/test_prf_finder.py'
--- lib/lp/registry/tests/test_prf_finder.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/tests/test_prf_finder.py	2012-11-30 10:13:01 +0000
@@ -60,6 +60,7 @@
                          ('product2', ['filter3', 'filter4']))
 
 
+<<<<<<< TREE
 class FindReleasesDBTestCase(TestCaseWithFactory):
 
     layer = LaunchpadZopelessLayer
@@ -88,6 +89,19 @@
         self.assertEqual(expected, found)
 
 
+=======
+class FindReleasesDBTestCase(TestCaseWithFactory):
+
+    layer = LaunchpadZopelessLayer
+
+    def test_findReleases_permissions(self):
+        switch_dbuser(config.productreleasefinder.dbuser)
+        prf = ProductReleaseFinder(self.layer.txn, logging.getLogger())
+        # Test that this raises no exceptions.
+        prf.findReleases()
+
+
+>>>>>>> MERGE-SOURCE
 class GetFiltersTestCase(TestCaseWithFactory):
 
     layer = LaunchpadZopelessLayer

=== modified file 'lib/lp/registry/tests/test_product.py'
--- lib/lp/registry/tests/test_product.py	2012-11-26 21:09:20 +0000
+++ lib/lp/registry/tests/test_product.py	2012-11-30 10:13:01 +0000
@@ -42,6 +42,7 @@
     IServiceUsage,
     )
 from lp.app.interfaces.services import IService
+<<<<<<< TREE
 from lp.blueprints.enums import (
     NewSpecificationDefinitionStatus,
     SpecificationDefinitionStatus,
@@ -53,6 +54,17 @@
 from lp.blueprints.model.specification import (
     SPECIFICATION_POLICY_ALLOWED_TYPES,
     )
+=======
+from lp.blueprints.enums import (
+    NewSpecificationDefinitionStatus,
+    SpecificationDefinitionStatus,
+    SpecificationFilter,
+    SpecificationImplementationStatus,
+    SpecificationPriority,
+    SpecificationSort,
+    )
+
+>>>>>>> MERGE-SOURCE
 from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
 from lp.bugs.interfaces.bugtarget import BUG_POLICY_ALLOWED_TYPES
@@ -697,6 +709,7 @@
                 CannotChangeInformationType, 'Answers is enabled.'):
                 product.information_type = InformationType.PROPRIETARY
 
+<<<<<<< TREE
     def test_no_proprietary_if_packaging(self):
         # information_type cannot be set to proprietary while any
         # productseries are packaged.
@@ -1138,6 +1151,450 @@
         self.assertIsNot(None, getUtility(IAccessPolicySource).find(
             [(product, InformationType.EMBARGOED)]).one())
 
+=======
+    def test_no_proprietary_if_packaging(self):
+        # information_type cannot be set to proprietary while any
+        # productseries are packaged.
+        product = self.factory.makeProduct(
+            licenses=[License.OTHER_PROPRIETARY])
+        series = self.factory.makeProductSeries(product=product)
+        self.factory.makePackagingLink(productseries=series)
+        with person_logged_in(product.owner):
+            with ExpectedException(
+                CannotChangeInformationType, 'Some series are packaged.'):
+                product.information_type = InformationType.PROPRIETARY
+
+    expected_get_permissions = {
+        CheckerPublic: set((
+            'active', 'id', 'information_type', 'pillar_category', 'private',
+            'userCanView',)),
+        'launchpad.LimitedView': set((
+            'bugtargetdisplayname', 'displayname', 'enable_bug_expiration',
+            'logo', 'name', 'official_answers', 'official_anything',
+            'official_blueprints', 'official_codehosting', 'official_malone',
+            'owner', 'parent_subscription_target', 'project', 'title', )),
+        'launchpad.View': set((
+            '_getOfficialTagClause', '_all_specifications',
+            '_valid_specifications', 'active_or_packaged_series',
+            'aliases', 'all_milestones',
+            'allowsTranslationEdits', 'allowsTranslationSuggestions',
+            'announce', 'answer_contacts', 'answers_usage', 'autoupdate',
+            'blueprints_usage', 'branch_sharing_policy',
+            'bug_reported_acknowledgement', 'bug_reporting_guidelines',
+            'bug_sharing_policy', 'bug_subscriptions', 'bug_supervisor',
+            'bug_tracking_usage', 'bugtargetname',
+            'bugtracker', 'canUserAlterAnswerContact', 'codehosting_usage',
+            'coming_sprints', 'commercial_subscription',
+            'commercial_subscription_is_due', 'createBug',
+            'createCustomLanguageCode', 'custom_language_codes',
+            'date_next_suggest_packaging', 'datecreated', 'description',
+            'development_focus', 'development_focusID',
+            'direct_answer_contacts', 'distrosourcepackages',
+            'downloadurl', 'driver', 'drivers',
+            'enable_bugfiling_duplicate_search', 'findReferencedOOPS',
+            'findSimilarFAQs', 'findSimilarQuestions', 'freshmeatproject',
+            'getAllowedBugInformationTypes',
+            'getAllowedSpecificationInformationTypes', 'getAnnouncement',
+            'getAnnouncements', 'getAnswerContactsForLanguage',
+            'getAnswerContactRecipients', 'getBranches',
+            'getBugSummaryContextWhereClause', 'getBugTaskWeightFunction',
+            'getCustomLanguageCode', 'getDefaultBugInformationType',
+            'getDefaultSpecificationInformationType',
+            'getEffectiveTranslationPermission', 'getExternalBugTracker',
+            'getFAQ', 'getFirstEntryToImport', 'getLinkedBugWatches',
+            'getMergeProposals', 'getMilestone', 'getMilestonesAndReleases',
+            'getQuestion', 'getQuestionLanguages', 'getPackage', 'getRelease',
+            'getSeries', 'getSpecification', 'getSubscription',
+            'getSubscriptions', 'getSupportedLanguages', 'getTimeline',
+            'getTopContributors', 'getTopContributorsGroupedByCategory',
+            'getTranslationGroups', 'getTranslationImportQueueEntries',
+            'getTranslators', 'getUsedBugTagsWithOpenCounts',
+            'getVersionSortedSeries',
+            'has_current_commercial_subscription',
+            'has_custom_language_codes', 'has_milestones', 'homepage_content',
+            'homepageurl', 'icon', 'invitesTranslationEdits',
+            'invitesTranslationSuggestions',
+            'license_info', 'license_status', 'licenses', 'milestones',
+            'mugshot', 'name_with_project', 'newCodeImport',
+            'obsolete_translatable_series', 'official_bug_tags',
+            'packagedInDistros', 'packagings',
+            'past_sprints', 'personHasDriverRights', 'pillar',
+            'primary_translatable', 'private_bugs',
+            'programminglang', 'qualifies_for_free_hosting',
+            'recipes', 'redeemSubscriptionVoucher', 'registrant', 'releases',
+            'remote_product', 'removeCustomLanguageCode',
+            'screenshotsurl',
+            'searchFAQs', 'searchQuestions', 'searchTasks', 'security_contact',
+            'series',
+            'sharesTranslationsWithOtherSide', 'sourceforgeproject',
+            'sourcepackages', 'specification_sharing_policy', 'specifications',
+            'sprints', 'summary', 'target_type_display',
+            'translatable_packages', 'translatable_series',
+            'translation_focus', 'translationgroup', 'translationgroups',
+            'translationpermission', 'translations_usage', 'ubuntu_packages',
+            'userCanAlterBugSubscription', 'userCanAlterSubscription',
+            'userCanEdit', 'userHasBugSubscriptions', 'uses_launchpad',
+            'wikiurl')),
+        'launchpad.AnyAllowedPerson': set((
+            'addAnswerContact', 'addBugSubscription',
+            'addBugSubscriptionFilter', 'addSubscription',
+            'createQuestionFromBug', 'newQuestion', 'removeAnswerContact',
+            'removeBugSubscription')),
+        'launchpad.Append': set(('newFAQ', )),
+        'launchpad.Driver': set(('newSeries', )),
+        'launchpad.Edit': set((
+            'addOfficialBugTag', 'removeOfficialBugTag',
+            'setBranchSharingPolicy', 'setBugSharingPolicy',
+            'setSpecificationSharingPolicy')),
+        'launchpad.Moderate': set((
+            'is_permitted', 'license_approved', 'project_reviewed',
+            'reviewer_whiteboard', 'setAliases')),
+        }
+
+    def test_get_permissions(self):
+        product = self.factory.makeProduct()
+        checker = getChecker(product)
+        self.checkPermissions(
+            self.expected_get_permissions, checker.get_permissions, 'get')
+
+    def test_set_permissions(self):
+        expected_set_permissions = {
+            'launchpad.BugSupervisor': set((
+                'bug_reported_acknowledgement', 'bug_reporting_guidelines',
+                'bugtracker', 'enable_bug_expiration',
+                'enable_bugfiling_duplicate_search', 'official_bug_tags',
+                'official_malone', 'remote_product')),
+            'launchpad.Edit': set((
+                'answers_usage', 'blueprints_usage', 'bug_supervisor',
+                'bug_tracking_usage', 'codehosting_usage',
+                'commercial_subscription', 'description', 'development_focus',
+                'displayname', 'downloadurl', 'driver', 'freshmeatproject',
+                'homepage_content', 'homepageurl', 'icon', 'information_type',
+                'license_info', 'licenses', 'logo', 'mugshot',
+                'official_answers', 'official_blueprints',
+                'official_codehosting', 'owner', 'private',
+                'programminglang', 'project', 'redeemSubscriptionVoucher',
+                'releaseroot', 'screenshotsurl', 'sourceforgeproject',
+                'summary', 'title', 'uses_launchpad', 'wikiurl')),
+            'launchpad.Moderate': set((
+                'active', 'autoupdate', 'license_approved', 'name',
+                'project_reviewed', 'registrant', 'reviewer_whiteboard')),
+            'launchpad.TranslationsAdmin': set((
+                'translation_focus', 'translationgroup',
+                'translationpermission', 'translations_usage')),
+            'launchpad.AnyAllowedPerson': set((
+                'date_next_suggest_packaging', )),
+            }
+        product = self.factory.makeProduct()
+        checker = getChecker(product)
+        self.checkPermissions(
+            expected_set_permissions, checker.set_permissions, 'set')
+
+    def test_access_launchpad_View_public_product(self):
+        # Everybody, including anonymous users, has access to
+        # properties of public products that require the permission
+        # launchpad.View
+        product = self.factory.makeProduct()
+        names = self.expected_get_permissions['launchpad.View']
+        with person_logged_in(None):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        ordinary_user = self.factory.makePerson()
+        with person_logged_in(ordinary_user):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        with person_logged_in(product.owner):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+
+    def test_access_launchpad_View_public_inactive_product(self):
+        # Everybody, including anonymous users, has access to
+        # properties of public but inactvie products that require
+        # the permission launchpad.View.
+        product = self.factory.makeProduct()
+        removeSecurityProxy(product).active = False
+        names = self.expected_get_permissions['launchpad.View']
+        with person_logged_in(None):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        ordinary_user = self.factory.makePerson()
+        with person_logged_in(ordinary_user):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        with person_logged_in(product.owner):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+
+    def test_access_launchpad_View_proprietary_product(self):
+        # Only people with grants for a private product can access
+        # attributes protected by the permission launchpad.View.
+        product = self.createProduct(
+            information_type=InformationType.PROPRIETARY,
+            license=License.OTHER_PROPRIETARY)
+        owner = removeSecurityProxy(product).owner
+        names = self.expected_get_permissions['launchpad.View']
+        with person_logged_in(None):
+            for attribute_name in names:
+                self.assertRaises(
+                    Unauthorized, getattr, product, attribute_name)
+        ordinary_user = self.factory.makePerson()
+        with person_logged_in(ordinary_user):
+            for attribute_name in names:
+                self.assertRaises(
+                    Unauthorized, getattr, product, attribute_name)
+        with person_logged_in(owner):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        # A user with a policy grant for the product can access attributes
+        # of a private product.
+        with person_logged_in(owner):
+            getUtility(IService, 'sharing').sharePillarInformation(
+                product, ordinary_user, owner,
+                {InformationType.PROPRIETARY: SharingPermission.ALL})
+        with person_logged_in(ordinary_user):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        # Access can be granted to a team too.
+        other_user = self.factory.makePerson()
+        team = self.factory.makeTeam(members=[other_user])
+        with person_logged_in(owner):
+            getUtility(IService, 'sharing').sharePillarInformation(
+                product, team, owner,
+                {InformationType.PROPRIETARY: SharingPermission.ALL})
+        with person_logged_in(other_user):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        # Admins can access proprietary products.
+        with celebrity_logged_in('admin'):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        # Commercial admins have access to all products.
+        with celebrity_logged_in('commercial_admin'):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+
+    def test_admin_launchpad_View_proprietary_product(self):
+        # Admins and commercial admins can view proprietary products.
+        product = self.factory.makeProduct(
+            information_type=InformationType.PROPRIETARY)
+        names = self.expected_get_permissions['launchpad.View']
+        with person_logged_in(self.factory.makeAdministrator()):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        with person_logged_in(self.factory.makeCommercialAdmin()):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+
+    def test_access_LimitedView_public_product(self):
+        # Everybody can access attributes of public products that
+        # require the permission launchpad.LimitedView.
+        product = self.factory.makeProduct()
+        names = self.expected_get_permissions['launchpad.LimitedView']
+        with person_logged_in(None):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        ordinary_user = self.factory.makePerson()
+        with person_logged_in(ordinary_user):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+
+    def test_access_LimitedView_proprietary_product(self):
+        # Anonymous users and ordinary logged in users cannot access
+        # attributes of private products that require the permission
+        # launchpad.LimitedView.
+        owner = self.factory.makePerson()
+        product = self.factory.makeProduct(
+            owner=owner,
+            information_type=InformationType.PROPRIETARY)
+        names = self.expected_get_permissions['launchpad.LimitedView']
+        with person_logged_in(None):
+            for attribute_name in names:
+                self.assertRaises(
+                    Unauthorized, getattr, product, attribute_name)
+        user = self.factory.makePerson()
+        with person_logged_in(user):
+            for attribute_name in names:
+                self.assertRaises(
+                    Unauthorized, getattr, product, attribute_name)
+        # Users with a grant on an artifact related to the product
+        # can access the attributes.
+        with person_logged_in(owner):
+            bug = self.factory.makeBug(
+                target=product, information_type=InformationType.PROPRIETARY)
+            getUtility(IService, 'sharing').ensureAccessGrants(
+                [user], owner, bugs=[bug])
+        with person_logged_in(user):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        # Users with a policy grant for the product also have access.
+        user2 = self.factory.makePerson()
+        with person_logged_in(owner):
+            getUtility(IService, 'sharing').sharePillarInformation(
+                product, user2, owner,
+                {InformationType.PROPRIETARY: SharingPermission.ALL})
+        with person_logged_in(user2):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+
+    def test_access_launchpad_AnyAllowedPerson_public_product(self):
+        # Only logged in persons have access to properties of public products
+        # that require the permission launchpad.AnyAllowedPerson.
+        product = self.factory.makeProduct()
+        names = self.expected_get_permissions['launchpad.AnyAllowedPerson']
+        with person_logged_in(None):
+            for attribute_name in names:
+                self.assertRaises(
+                    Unauthorized, getattr, product, attribute_name)
+        ordinary_user = self.factory.makePerson()
+        with person_logged_in(ordinary_user):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        with person_logged_in(product.owner):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+
+    def test_access_launchpad_AnyAllowedPerson_proprietary_product(self):
+        # Only people with grants for a private product can access
+        # attributes protected by the permission launchpad.AnyAllowedPerson.
+        product = self.createProduct(
+            information_type=InformationType.PROPRIETARY,
+            license=License.OTHER_PROPRIETARY)
+        owner = removeSecurityProxy(product).owner
+        names = self.expected_get_permissions['launchpad.AnyAllowedPerson']
+        with person_logged_in(None):
+            for attribute_name in names:
+                self.assertRaises(
+                    Unauthorized, getattr, product, attribute_name)
+        ordinary_user = self.factory.makePerson()
+        with person_logged_in(ordinary_user):
+            for attribute_name in names:
+                self.assertRaises(
+                    Unauthorized, getattr, product, attribute_name)
+        with person_logged_in(owner):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+        # A user with a policy grant for the product can access attributes
+        # of a private product.
+        with person_logged_in(owner):
+            getUtility(IService, 'sharing').sharePillarInformation(
+                product, ordinary_user, owner,
+                {InformationType.PROPRIETARY: SharingPermission.ALL})
+        with person_logged_in(ordinary_user):
+            for attribute_name in names:
+                getattr(product, attribute_name)
+
+    def test_set_launchpad_AnyAllowedPerson_public_product(self):
+        # Only logged in users can set attributes protected by the
+        # permission launchpad.AnyAllowedPerson.
+        product = self.factory.makeProduct()
+        with person_logged_in(None):
+            self.assertRaises(
+                Unauthorized, setattr, product, 'date_next_suggest_packaging',
+                'foo')
+        ordinary_user = self.factory.makePerson()
+        with person_logged_in(ordinary_user):
+            setattr(product, 'date_next_suggest_packaging', 'foo')
+        with person_logged_in(product.owner):
+            setattr(product, 'date_next_suggest_packaging', 'foo')
+
+    def test_set_launchpad_AnyAllowedPerson_proprietary_product(self):
+        # Only people with grants for a private product can set
+        # attributes protected by the permission launchpad.AnyAllowedPerson.
+        product = self.createProduct(
+            information_type=InformationType.PROPRIETARY,
+            license=License.OTHER_PROPRIETARY)
+        owner = removeSecurityProxy(product).owner
+        with person_logged_in(None):
+            self.assertRaises(
+                Unauthorized, setattr, product, 'date_next_suggest_packaging',
+                'foo')
+        ordinary_user = self.factory.makePerson()
+        with person_logged_in(ordinary_user):
+            self.assertRaises(
+                Unauthorized, setattr, product, 'date_next_suggest_packaging',
+                'foo')
+        with person_logged_in(owner):
+            setattr(product, 'date_next_suggest_packaging', 'foo')
+        # A user with a policy grant for the product can access attributes
+        # of a private product.
+        with person_logged_in(owner):
+            getUtility(IService, 'sharing').sharePillarInformation(
+                product, ordinary_user, owner,
+                {InformationType.PROPRIETARY: SharingPermission.ALL})
+        with person_logged_in(ordinary_user):
+            setattr(product, 'date_next_suggest_packaging', 'foo')
+
+    def test_userCanView_caches_known_users(self):
+        # userCanView() maintains a cache of users known to have the
+        # permission to access a product.
+        product = self.createProduct(
+            information_type=InformationType.PROPRIETARY,
+            license=License.OTHER_PROPRIETARY)
+        owner = removeSecurityProxy(product).owner
+        user = self.factory.makePerson()
+        with person_logged_in(owner):
+            getUtility(IService, 'sharing').sharePillarInformation(
+                product, user, owner,
+                {InformationType.PROPRIETARY: SharingPermission.ALL})
+        with person_logged_in(user):
+            with StormStatementRecorder() as recorder:
+                # The first access to a property of the product from
+                # a user requires a DB query.
+                product.homepageurl
+                queries_for_first_user_access = len(recorder.queries)
+                # The second access does not require another query.
+                product.description
+                self.assertEqual(
+                queries_for_first_user_access, len(recorder.queries))
+
+    def test_userCanView_works_with_IPersonRoles(self):
+        # userCanView() maintains a cache of users known to have the
+        # permission to access a product.
+        product = self.createProduct(
+            information_type=InformationType.PROPRIETARY,
+            license=License.OTHER_PROPRIETARY)
+        user = self.factory.makePerson()
+        product.userCanView(user)
+        product.userCanView(IPersonRoles(user))
+
+    def test_userCanView_override(self):
+        # userCanView is overridden by the traversal override.
+        product = self.factory.makeProduct(
+            information_type=InformationType.PROPRIETARY)
+        unprivileged = self.factory.makePerson()
+        with person_logged_in(unprivileged):
+            with FeatureFixture(
+                {'disclosure.private_project.traversal_override': 'on'}):
+                self.assertTrue(product.userCanView(unprivileged))
+            self.assertFalse(product.userCanView(unprivileged))
+
+    def test_anonymous_traversal_override(self):
+        # The traversal override affects the permissions granted to anonymous
+        # users.
+        product = self.factory.makeProduct(
+            information_type=InformationType.PROPRIETARY)
+        with person_logged_in(None):
+            with FeatureFixture(
+                {'disclosure.private_project.traversal_override': 'on'}):
+                self.assertTrue(check_permission('launchpad.View', product))
+            self.assertFalse(check_permission('launchpad.View', product))
+
+    def test_information_type_prevents_pruning(self):
+        # Access policies for Product.information_type are not pruned.
+        owner = self.factory.makePerson()
+        for info_type in [
+            InformationType.PROPRIETARY, InformationType.EMBARGOED]:
+            product = self.factory.makeProduct(
+                information_type=info_type, owner=owner)
+            with person_logged_in(owner):
+                product.setBugSharingPolicy(BugSharingPolicy.PUBLIC)
+                product.setSpecificationSharingPolicy(
+                    SpecificationSharingPolicy.PUBLIC)
+                product.setBranchSharingPolicy(BranchSharingPolicy.PUBLIC)
+            self.assertIsNot(None, getUtility(IAccessPolicySource).find(
+                [(product, info_type)]).one())
+
+>>>>>>> MERGE-SOURCE
 
 class TestProductBugInformationTypes(TestCaseWithFactory):
 
@@ -2108,6 +2565,7 @@
         self.failUnlessEqual(
             [],
             ws_product.findReferencedOOPS(start_date=now - day, end_date=now))
+<<<<<<< TREE
 
 
 class TestProductSet(TestCaseWithFactory):
@@ -2304,3 +2762,161 @@
         self.assertIn(name, [p.name for p in productset.search()])
         productset = self.wsObject(ProductSet(), self.factory.makePerson())
         self.assertNotIn(name, [p.name for p in productset.search()])
+=======
+
+
+class TestProductSet(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def makeAllInformationTypes(self):
+        proprietary = self.factory.makeProduct(
+            information_type=InformationType.PROPRIETARY)
+        embargoed = self.factory.makeProduct(
+            information_type=InformationType.EMBARGOED)
+        public = self.factory.makeProduct(
+            information_type=InformationType.PUBLIC)
+        return proprietary, embargoed, public
+
+    @staticmethod
+    def filterFind(user):
+        clause = ProductSet.getProductPrivacyFilter(user)
+        return IStore(Product).find(Product, clause)
+
+    def test_users_private_products(self):
+        # Ignore any public products the user may own.
+        owner = self.factory.makePerson()
+        self.factory.makeProduct(
+            information_type=InformationType.PUBLIC,
+            owner=owner)
+        proprietary = self.factory.makeProduct(
+            information_type=InformationType.PROPRIETARY,
+            owner=owner)
+        embargoed = self.factory.makeProduct(
+            information_type=InformationType.EMBARGOED,
+            owner=owner)
+        result = ProductSet.get_users_private_products(owner)
+        self.assertIn(proprietary, result)
+        self.assertIn(embargoed, result)
+
+    def test_get_all_active_omits_proprietary(self):
+        # Ignore proprietary products for anonymous users
+        proprietary = self.factory.makeProduct(
+            information_type=InformationType.PROPRIETARY)
+        embargoed = self.factory.makeProduct(
+            information_type=InformationType.EMBARGOED)
+        result = ProductSet.get_all_active(None)
+        self.assertNotIn(proprietary, result)
+        self.assertNotIn(embargoed, result)
+
+    def test_getProductPrivacyFilterAnonymous(self):
+        # Ignore proprietary products for anonymous users
+        proprietary, embargoed, public = self.makeAllInformationTypes()
+        result = self.filterFind(None)
+        self.assertIn(public, result)
+        self.assertNotIn(embargoed, result)
+        self.assertNotIn(proprietary, result)
+
+    def test_getProductPrivacyFilter_excludes_random_users(self):
+        # Exclude proprietary products for anonymous users
+        random = self.factory.makePerson()
+        proprietary, embargoed, public = self.makeAllInformationTypes()
+        result = self.filterFind(random)
+        self.assertIn(public, result)
+        self.assertNotIn(embargoed, result)
+        self.assertNotIn(proprietary, result)
+
+    def grant(self, pillar, information_type, grantee):
+        policy_source = getUtility(IAccessPolicySource)
+        (policy,) = policy_source.find(
+            [(pillar, information_type)])
+        self.factory.makeAccessPolicyGrant(policy, grantee)
+
+    def test_getProductPrivacyFilter_respects_grants(self):
+        # Include proprietary products for users with right grants.
+        grantee = self.factory.makePerson()
+        proprietary, embargoed, public = self.makeAllInformationTypes()
+        self.grant(embargoed, InformationType.EMBARGOED, grantee)
+        self.grant(proprietary, InformationType.PROPRIETARY, grantee)
+        result = self.filterFind(grantee)
+        self.assertIn(public, result)
+        self.assertIn(embargoed, result)
+        self.assertIn(proprietary, result)
+
+    def test_getProductPrivacyFilter_ignores_wrong_product(self):
+        # Exclude proprietary products if grant is on wrong product.
+        grantee = self.factory.makePerson()
+        proprietary, embargoed, public = self.makeAllInformationTypes()
+        self.factory.makeAccessPolicyGrant(grantee=grantee)
+        result = self.filterFind(grantee)
+        self.assertIn(public, result)
+        self.assertNotIn(embargoed, result)
+        self.assertNotIn(proprietary, result)
+
+    def test_getProductPrivacyFilter_ignores_wrong_info_type(self):
+        # Exclude proprietary products if grant is on wrong information type.
+        grantee = self.factory.makePerson()
+        proprietary, embargoed, public = self.makeAllInformationTypes()
+        self.grant(embargoed, InformationType.PROPRIETARY, grantee)
+        self.factory.makeAccessPolicy(proprietary, InformationType.EMBARGOED)
+        self.grant(proprietary, InformationType.EMBARGOED, grantee)
+        result = self.filterFind(grantee)
+        self.assertIn(public, result)
+        self.assertNotIn(embargoed, result)
+        self.assertNotIn(proprietary, result)
+
+    def test_getProductPrivacyFilter_respects_team_grants(self):
+        # Include proprietary products for users in teams with right grants.
+        grantee = self.factory.makeTeam()
+        proprietary, embargoed, public = self.makeAllInformationTypes()
+        self.grant(embargoed, InformationType.EMBARGOED, grantee)
+        self.grant(proprietary, InformationType.PROPRIETARY, grantee)
+        result = self.filterFind(grantee.teamowner)
+        self.assertIn(public, result)
+        self.assertIn(embargoed, result)
+        self.assertIn(proprietary, result)
+
+    def test_getProductPrivacyFilter_includes_admins(self):
+        # Launchpad admins can see everything.
+        proprietary, embargoed, public = self.makeAllInformationTypes()
+        result = self.filterFind(self.factory.makeAdministrator())
+        self.assertIn(public, result)
+        self.assertIn(embargoed, result)
+        self.assertIn(proprietary, result)
+
+    def test_getProductPrivacyFilter_includes_commercial_admins(self):
+        # Commercial admins can see everything.
+        proprietary, embargoed, public = self.makeAllInformationTypes()
+        result = self.filterFind(self.factory.makeCommercialAdmin())
+        self.assertIn(public, result)
+        self.assertIn(embargoed, result)
+        self.assertIn(proprietary, result)
+
+    def test_getTranslatables_filters_private_products(self):
+        # ProductSet.getTranslatables() returns rivate translatable
+        # products only for user that have grants for these products.
+        owner = self.factory.makePerson()
+        product = self.factory.makeProduct(
+            owner=owner, translations_usage=ServiceUsage.LAUNCHPAD)
+        series = self.factory.makeProductSeries(product)
+        self.factory.makePOTemplate(productseries=series)
+        with person_logged_in(owner):
+            product.information_type = InformationType.PROPRIETARY
+        # Anonymous users do not see private products.
+        with person_logged_in(ANONYMOUS):
+            translatables = getUtility(IProductSet).getTranslatables()
+            self.assertNotIn(product, list(translatables))
+        # Ordinary users do not see private products.
+        user = self.factory.makePerson()
+        with person_logged_in(user):
+            translatables = getUtility(IProductSet).getTranslatables()
+            self.assertNotIn(product, list(translatables))
+        # Users with policy grants on private products see them.
+        with person_logged_in(owner):
+            getUtility(IService, 'sharing').sharePillarInformation(
+                product, user, owner,
+                {InformationType.PROPRIETARY: SharingPermission.ALL})
+        with person_logged_in(user):
+            translatables = getUtility(IProductSet).getTranslatables()
+            self.assertIn(product, list(translatables))
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/registry/tests/test_product_webservice.py'
--- lib/lp/registry/tests/test_product_webservice.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/tests/test_product_webservice.py	2012-11-30 10:13:01 +0000
@@ -20,6 +20,7 @@
     TestCaseWithFactory,
     )
 from lp.testing.layers import DatabaseFunctionalLayer
+from lp.testing import person_logged_in
 from lp.testing.pages import (
     LaunchpadWebServiceCaller,
     webservice_for_person,

=== modified file 'lib/lp/registry/tests/test_productseries.py'
--- lib/lp/registry/tests/test_productseries.py	2012-11-27 22:02:34 +0000
+++ lib/lp/registry/tests/test_productseries.py	2012-11-30 10:13:01 +0000
@@ -8,12 +8,20 @@
 from storm.exceptions import NoneError
 from testtools.testcase import ExpectedException
 import transaction
+from zope.security.checker import (
+    CheckerPublic,
+    getChecker,
+    )
 from zope.component import getUtility
+<<<<<<< TREE
 from zope.security.checker import (
     CheckerPublic,
     getChecker,
     )
 from zope.security.interfaces import Unauthorized
+=======
+from zope.security.interfaces import Unauthorized
+>>>>>>> MERGE-SOURCE
 from zope.security.proxy import removeSecurityProxy
 
 from lp.app.enums import InformationType
@@ -574,6 +582,7 @@
         mode = TranslationsBranchImportMode.IMPORT_TRANSLATIONS
         ws_series.translations_autoimport_mode = mode.title
         ws_series.lp_save()
+<<<<<<< TREE
 
 
 class ProductSeriesSecurityAdaperTestCase(TestCaseWithFactory):
@@ -926,3 +935,295 @@
                 # No exception is raised when attributes of timeline
                 # are accessed.
                 getattr(timeline, name)
+=======
+
+
+class ProductSeriesSecurityAdaperTestCase(TestCaseWithFactory):
+    """Test for permissions of IProductSeries."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(ProductSeriesSecurityAdaperTestCase, self).setUp()
+        self.public_product = self.factory.makeProduct()
+        self.public_series = self.factory.makeProductSeries(
+            product=self.public_product)
+        self.proprietary_product_owner = self.factory.makePerson()
+        self.proprietary_product = self.factory.makeProduct(
+            owner=self.proprietary_product_owner,
+            information_type=InformationType.PROPRIETARY)
+        self.proprietary_series = self.factory.makeProductSeries(
+            product=self.proprietary_product)
+
+    expected_get_permissions = {
+        CheckerPublic: set((
+            'id', 'userCanView',
+            )),
+        'launchpad.AnyAllowedPerson': set((
+            'addBugSubscription', 'addBugSubscriptionFilter',
+            'addSubscription', 'removeBugSubscription',
+            )),
+        'launchpad.Edit': set(('newMilestone', )),
+        'launchpad.View': set((
+            '_all_specifications', '_getOfficialTagClause',
+            '_valid_specifications', 'active', 'all_milestones',
+            'answers_usage', 'blueprints_usage', 'branch',
+            'bug_reported_acknowledgement', 'bug_reporting_guidelines',
+            'bug_subscriptions', 'bug_supervisor', 'bug_tracking_usage',
+            'bugtargetdisplayname', 'bugtarget_parent', 'bugtargetname',
+            'codehosting_usage', 'createBug', 'datecreated', 'displayname',
+            'driver', 'drivers', 'enable_bugfiling_duplicate_search',
+            'getAllowedSpecificationInformationTypes',
+            'getBugSummaryContextWhereClause',
+            'getBugTaskWeightFunction', 'getCachedReleases',
+            'getCurrentTemplatesCollection', 'getCurrentTranslationFiles',
+            'getCurrentTranslationTemplates',
+            'getDefaultSpecificationInformationType',
+            'getFirstEntryToImport', 'getLatestRelease', 'getPOTemplate',
+            'getPackage', 'getPackagingInDistribution', 'getRelease',
+            'getSharingPartner', 'getSpecification', 'getSubscription',
+            'getSubscriptions', 'getTemplatesAndLanguageCounts',
+            'getTemplatesCollection', 'getTimeline',
+            'getTranslationImportQueueEntries',
+            'getTranslationTemplateByName', 'getTranslationTemplateFormats',
+            'getTranslationTemplates', 'getUbuntuTranslationFocusPackage',
+            'getUsedBugTagsWithOpenCounts',
+            'has_current_translation_templates', 'has_milestones',
+            'has_obsolete_translation_templates',
+            'has_sharing_translation_templates', 'has_translation_files',
+            'has_translation_templates', 'is_development_focus', 'milestones',
+            'name', 'official_bug_tags', 'owner', 'packagings', 'parent',
+            'parent_subscription_target',
+            'personHasDriverRights', 'pillar', 'potemplate_count', 'product',
+            'productID', 'productserieslanguages', 'release_files',
+            'releasefileglob', 'releases', 'releaseverstyle', 'searchTasks',
+            'series', 'setPackaging', 'sourcepackages', 'specifications',
+            'status', 'summary', 'target_type_display', 'title',
+            'translations_autoimport_mode', 'userCanAlterBugSubscription',
+            'userCanAlterSubscription', 'userHasBugSubscriptions',
+            'translations_branch', 'translations_usage', 'uses_launchpad',
+            )),
+        }
+
+    def test_get_permissions(self):
+        checker = getChecker(self.public_series)
+        self.checkPermissions(
+            self.expected_get_permissions, checker.get_permissions, 'get')
+
+    expected_set_permissions = {
+        'launchpad.Edit': set((
+            'answers_usage', 'blueprints_usage', 'branch',
+            'bug_tracking_usage', 'codehosting_usage', 'driver', 'name',
+            'owner', 'product', 'releasefileglob', 'status', 'summary',
+            'translations_autoimport_mode', 'translations_branch',
+            'translations_usage', 'uses_launchpad',
+            )),
+        'launchpad.AnyAllowedPerson': set((
+            'cvsbranch', 'cvsmodule', 'cvsroot', 'cvstarfileurl',
+            'importstatus', 'rcstype', 'svnrepository',
+            )),
+        }
+
+    def test_set_permissions(self):
+        checker = getChecker(self.public_series)
+        self.checkPermissions(
+            self.expected_set_permissions, checker.set_permissions, 'set')
+
+    def assertAccessAuthorized(self, attribute_names, obj):
+        # Try to access the given attributes of obj. No exception
+        # should be raised.
+        for name in attribute_names:
+            getattr(obj, name)
+
+    def assertAccessUnauthorized(self, attribute_names, obj):
+        # Try to access the given attributes of obj. Unauthorized
+        # should be raised.
+        for name in attribute_names:
+            self.assertRaises(Unauthorized, getattr, obj, name)
+
+    def assertChangeAuthorized(self, attribute_names, obj):
+        # Try to changes the given attributes of obj. Unauthorized
+        # should not be raised.
+        for name in attribute_names:
+            # Not all attributes declared in configure.zcml to be
+            # settable actually exist or are settable. Attempts to set
+            # them raise an AttributeError. Similary, the naive attempt
+            # to set an attribute to None may raise a NoneError
+            #
+            # Both errors can be ignored here: This method intends only
+            # to prove that Unauthorized is not raised.
+            try:
+                setattr(obj, name, None)
+            except (AttributeError, NoneError):
+                pass
+
+    def assertChangeUnauthorized(self, attribute_names, obj):
+        # Try to changes the given attributes of obj. Unauthorized
+        # should be raised.
+        for name in attribute_names:
+            self.assertRaises(Unauthorized, setattr, obj, name, None)
+
+    def test_access_for_anonymous(self):
+        # Anonymous users have access to public attributes of
+        # a series for a private or public product.
+        with person_logged_in(ANONYMOUS):
+            self.assertAccessAuthorized(
+                self.expected_get_permissions[CheckerPublic],
+                self.public_series)
+            self.assertAccessAuthorized(
+                self.expected_get_permissions[CheckerPublic],
+                self.proprietary_series)
+
+            # They have access to attributes requiring the permission
+            # launchpad.View of a series for a public product...
+            self.assertAccessAuthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.public_series)
+
+            # ...but not to the same attributes of a series for s private
+            # product.
+            self.assertAccessUnauthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.proprietary_series)
+
+            # The cannot access other attributes of a series for
+            # public and private products.
+            for permission, names in self.expected_get_permissions.items():
+                if permission in (CheckerPublic, 'launchpad.View'):
+                    continue
+                self.assertAccessUnauthorized(names, self.public_series)
+                self.assertAccessUnauthorized(names, self.proprietary_series)
+
+            # They cannot change any attributes.
+            for permission, names in self.expected_set_permissions.items():
+                self.assertChangeUnauthorized(names, self.public_series)
+                self.assertChangeUnauthorized(names, self.proprietary_series)
+
+    def test_access_for_regular_user(self):
+        # Regular users have access to public attributes of
+        # a series for a private or public product.
+        user = self.factory.makePerson()
+        with person_logged_in(user):
+            self.assertAccessAuthorized(
+                self.expected_get_permissions[CheckerPublic],
+                self.public_series)
+            self.assertAccessAuthorized(
+                self.expected_get_permissions[CheckerPublic],
+                self.proprietary_series)
+
+            # They have access to attributes requiring the permissions
+            # launchpad.View and launchpadAnyAllowedPerson of a series
+            # for a public product...
+            self.assertAccessAuthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.public_series)
+            self.assertAccessAuthorized(
+                self.expected_get_permissions['launchpad.AnyAllowedPerson'],
+                self.public_series)
+
+            # ...but not to the same attributes of a series for s private
+            # product.
+            self.assertAccessUnauthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.proprietary_series)
+            self.assertAccessUnauthorized(
+                self.expected_get_permissions['launchpad.AnyAllowedPerson'],
+                self.proprietary_series)
+
+            # The cannot access other attributes of a series for
+            # public and private products.
+            for permission, names in self.expected_get_permissions.items():
+                if permission in (CheckerPublic, 'launchpad.View',
+                                  'launchpad.AnyAllowedPerson'):
+                    continue
+                self.assertAccessUnauthorized(names, self.public_series)
+                self.assertAccessUnauthorized(names, self.proprietary_series)
+
+            # They can change attributes requiring the permission
+            # launchpad.AnyAllowedPerson of a series for a public project...
+            self.assertChangeAuthorized(
+                self.expected_set_permissions['launchpad.AnyAllowedPerson'],
+                self.public_series)
+            #... but not for a private project.
+            self.assertChangeUnauthorized(
+                self.expected_set_permissions['launchpad.AnyAllowedPerson'],
+                self.proprietary_series)
+
+            # They cannot change any attributes that require another
+            # permission than launchpad.AnyALlowedPerson.
+            for permission, names in self.expected_set_permissions.items():
+                if permission == 'launchpad.AnyAllowedPerson':
+                    continue
+                self.assertChangeUnauthorized(names, self.public_series)
+                self.assertChangeUnauthorized(names, self.proprietary_series)
+
+    def test_access_for_user_with_policy_grant(self):
+        # Users with a policy grant for the parent product can access
+        # properties requring the permission lanchpad.View or
+        # launchpad.ANyALlowedPerson of a series.
+        user = self.factory.makePerson()
+        with person_logged_in(self.proprietary_product_owner):
+            getUtility(IService, 'sharing').sharePillarInformation(
+                self.proprietary_product, user, self.proprietary_product_owner,
+                {InformationType.PROPRIETARY: SharingPermission.ALL})
+        with person_logged_in(user):
+            self.assertAccessAuthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.proprietary_series)
+            self.assertAccessAuthorized(
+                self.expected_get_permissions['launchpad.AnyAllowedPerson'],
+                self.proprietary_series)
+
+            # The cannot access other attributes of a series for
+            # private products.
+            for permission, names in self.expected_get_permissions.items():
+                if permission in (CheckerPublic, 'launchpad.View',
+                                  'launchpad.AnyAllowedPerson'):
+                    continue
+                self.assertAccessUnauthorized(names, self.proprietary_series)
+
+            # They can change attributes requiring the permission
+            # launchpad.AnyAllowedPerson of a series for a provate project...
+            self.assertChangeAuthorized(
+                self.expected_set_permissions['launchpad.AnyAllowedPerson'],
+                self.proprietary_series)
+
+            # They cannot change any attributes that require another
+            # permission than launchpad.AnyALlowedPerson.
+            for permission, names in self.expected_set_permissions.items():
+                if permission == 'launchpad.AnyAllowedPerson':
+                    continue
+                self.assertChangeUnauthorized(names, self.proprietary_series)
+
+    def test_access_for_product_owner(self):
+        # The owner of a project has access to all attributes of
+        # a product series.
+        with person_logged_in(self.proprietary_product_owner):
+            for permission, names in self.expected_get_permissions.items():
+                self.assertAccessAuthorized(names, self.proprietary_series)
+
+            # They can change all attributes.
+            for permission, names in self.expected_set_permissions.items():
+                self.assertChangeAuthorized(names, self.proprietary_series)
+
+        with person_logged_in(self.public_product.owner):
+            for permission, names in self.expected_get_permissions.items():
+                self.assertAccessAuthorized(names, self.public_series)
+
+            # They can change all attributes.
+            for permission, names in self.expected_set_permissions.items():
+                self.assertChangeAuthorized(names, self.public_series)
+
+    def test_access_for_lp_admins(self):
+        # Launchpad admins can access and change any attribute of a series
+        # of public and private product.
+        with celebrity_logged_in('admin'):
+            for permission, names in self.expected_get_permissions.items():
+                self.assertAccessAuthorized(names, self.public_series)
+                self.assertAccessAuthorized(names, self.proprietary_series)
+
+            # They can change all attributes.
+            for permission, names in self.expected_set_permissions.items():
+                self.assertChangeAuthorized(names, self.public_series)
+                self.assertChangeAuthorized(names, self.proprietary_series)
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/registry/tests/test_sourcepackage.py'
--- lib/lp/registry/tests/test_sourcepackage.py	2012-11-26 08:33:03 +0000
+++ lib/lp/registry/tests/test_sourcepackage.py	2012-11-30 10:13:01 +0000
@@ -12,6 +12,7 @@
 from storm.locals import Store
 from testtools.testcase import ExpectedException
 import transaction
+from testtools.testcase import ExpectedException
 from zope.component import getUtility
 from zope.interface.verify import verifyObject
 from zope.security.checker import canAccess

=== modified file 'lib/lp/registry/tests/test_subscribers.py'
=== modified file 'lib/lp/registry/vocabularies.py'
=== modified file 'lib/lp/scripts/garbo.py'
--- lib/lp/scripts/garbo.py	2012-11-26 08:33:03 +0000
+++ lib/lp/scripts/garbo.py	2012-11-30 10:13:01 +0000
@@ -19,6 +19,7 @@
 import logging
 import multiprocessing
 import os
+import simplejson
 import threading
 import time
 

=== modified file 'lib/lp/scripts/tests/test_garbo.py'
--- lib/lp/scripts/tests/test_garbo.py	2012-11-26 08:33:03 +0000
+++ lib/lp/scripts/tests/test_garbo.py	2012-11-30 10:13:01 +0000
@@ -11,6 +11,7 @@
     timedelta,
     )
 import logging
+import pytz
 from StringIO import StringIO
 import time
 

=== modified file 'lib/lp/security.py'
--- lib/lp/security.py	2012-11-27 22:02:34 +0000
+++ lib/lp/security.py	2012-11-30 10:13:01 +0000
@@ -38,6 +38,7 @@
     AuthorizationBase,
     DelegatedAuthorization,
     )
+from lp.app.interfaces.services import IService
 from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfig
 from lp.blueprints.interfaces.specification import (
     ISpecification,

=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py	2012-11-19 05:54:50 +0000
+++ lib/lp/services/features/flags.py	2012-11-30 10:13:01 +0000
@@ -238,6 +238,17 @@
      'Traversal to private projects requires special access.',
      'Override traveral checks.',
      'https://dev.launchpad.net/LEP/PrivateProjects'),
+<<<<<<< TREE
+=======
+    ('registry.fast_related_software.enabled',
+     'boolean',
+     ('If true, use the new reporting cache to load a person\'s related '
+      'software information.'),
+     '',
+     '',
+     ''),
+
+>>>>>>> MERGE-SOURCE
     ])
 
 # The set of all flag names that are documented.

=== modified file 'lib/lp/services/job/runner.py'
--- lib/lp/services/job/runner.py	2012-11-26 08:33:03 +0000
+++ lib/lp/services/job/runner.py	2012-11-30 10:13:01 +0000
@@ -35,6 +35,7 @@
     SIGHUP,
     signal,
     )
+from storm.exceptions import LostObjectError
 import sys
 from textwrap import dedent
 from uuid import uuid4

=== modified file 'lib/lp/services/messaging/rabbit.py'
--- lib/lp/services/messaging/rabbit.py	2012-11-26 08:33:03 +0000
+++ lib/lp/services/messaging/rabbit.py	2012-11-30 10:13:01 +0000
@@ -16,6 +16,7 @@
 import json
 import socket
 import sys
+import socket
 import threading
 import time
 

=== modified file 'lib/lp/services/webapp/publisher.py'
=== modified file 'lib/lp/soyuz/browser/archive.py'
=== modified file 'lib/lp/soyuz/configure.zcml'
=== modified file 'lib/lp/soyuz/interfaces/reporting.py'
--- lib/lp/soyuz/interfaces/reporting.py	2012-11-26 08:33:03 +0000
+++ lib/lp/soyuz/interfaces/reporting.py	2012-11-30 10:13:01 +0000
@@ -1,31 +1,64 @@
-# Copyright 2012 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-__metaclass__ = type
-__all__ = [
-    'ILatestPersonSourcePackageReleaseCache',
-    ]
-
-
-from zope.interface import Attribute
-
-from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
-
-
-class ILatestPersonSourcePackageReleaseCache(ISourcePackageRelease):
-    """Published source package release information for a person.
-
-    The records represented by this object are the latest published source
-    package releases for a given sourcepackage, distroseries, archive, keyed
-    on the package creator and maintainer. The table contains a set of data
-    records for package creators and a second set of data for package
-    maintainers. Queries can be filtered by creator or maintainer as required.
-    """
-
-    cache_id = Attribute(
-        "The id of the associated LatestPersonSourcePackageReleaseCache"
-        "record.")
-    sourcepackagerelease = Attribute(
-        "The SourcePackageRelease which this object represents.")
-    publication = Attribute(
-        "The publication record for the associated SourcePackageRelease.")
+<<<<<<< TREE
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+__all__ = [
+    'ILatestPersonSourcePackageReleaseCache',
+    ]
+
+
+from zope.interface import Attribute
+
+from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
+
+
+class ILatestPersonSourcePackageReleaseCache(ISourcePackageRelease):
+    """Published source package release information for a person.
+
+    The records represented by this object are the latest published source
+    package releases for a given sourcepackage, distroseries, archive, keyed
+    on the package creator and maintainer. The table contains a set of data
+    records for package creators and a second set of data for package
+    maintainers. Queries can be filtered by creator or maintainer as required.
+    """
+
+    cache_id = Attribute(
+        "The id of the associated LatestPersonSourcePackageReleaseCache"
+        "record.")
+    sourcepackagerelease = Attribute(
+        "The SourcePackageRelease which this object represents.")
+    publication = Attribute(
+        "The publication record for the associated SourcePackageRelease.")
+=======
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+__all__ = [
+    'ILatestPersonSourcePackageReleaseCache',
+    ]
+
+
+from zope.interface import Attribute
+from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
+
+
+class ILatestPersonSourcePackageReleaseCache(ISourcePackageRelease):
+    """Published source package release information for a person.
+
+    The records represented by this object are the latest published source
+    package releases for a given sourcepackage, distroseries, archive, keyed
+    on the package creator and maintainer. The table contains a set of data
+    records for package creators and a second set of data for package
+    maintainers. Queries can be filtered by creator or maintainer as required.
+    """
+
+    cache_id = Attribute(
+        "The id of the associated LatestPersonSourcePackageReleaseCache"
+        "record.")
+    sourcepackagerelease = Attribute(
+        "The SourcePackageRelease which this object represents.")
+    publication = Attribute(
+        "The publication record for the associated SourcePackageRelease.")
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/soyuz/model/queue.py'
=== modified file 'lib/lp/soyuz/scripts/packagecopier.py'
=== modified file 'lib/lp/soyuz/stories/soyuz/xx-person-packages.txt'
--- lib/lp/soyuz/stories/soyuz/xx-person-packages.txt	2012-11-19 07:31:45 +0000
+++ lib/lp/soyuz/stories/soyuz/xx-person-packages.txt	2012-11-30 10:13:01 +0000
@@ -160,6 +160,7 @@
     ...     for row in rows:
     ...         print extract_text(row)
 
+<<<<<<< TREE
 Make a function to update the cached latest person source package release
 records.
 
@@ -187,6 +188,33 @@
     ...         job(chunk_size=100)
     ...     switch_dbuser('launchpad')
 
+=======
+Make a function to update the cached latest person source package release
+records.
+
+    >>> from lp.scripts.garbo import PopulateLatestPersonSourcePackageReleaseCache
+    >>> from lp.services.database.sqlbase import flush_database_updates
+    >>> from lp.services.log.logger import FakeLogger
+    >>> from lp.testing.dbuser import switch_dbuser
+    >>> from lp.services.database.interfaces import (
+    ...     IStoreSelector,
+    ...     MAIN_STORE,
+    ...     MASTER_FLAVOR,
+    ...     )
+
+    >>> def update_cached_records(delete_all=False):
+    ...     store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
+    ...     if delete_all:
+    ...         store.execute(
+    ...             "delete from latestpersonsourcepackagereleasecache")
+    ...     flush_database_updates()
+    ...     switch_dbuser('garbo_frequently')
+    ...     job = PopulateLatestPersonSourcePackageReleaseCache(FakeLogger())
+    ...     while not job.isDone():
+    ...         job(chunk_size=100)
+    ...     switch_dbuser('launchpad')
+
+>>>>>>> MERGE-SOURCE
 
 Create some new source packages, source1 and source2, both created by cprov
 so that they appear in his +packages page.

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2012-11-22 03:57:45 +0000
+++ lib/lp/testing/factory.py	2012-11-30 10:13:01 +0000
@@ -1025,6 +1025,11 @@
         if specification_sharing_policy:
             naked_product.setSpecificationSharingPolicy(
                 specification_sharing_policy)
+<<<<<<< TREE
+=======
+        if information_type is not None:
+            naked_product.information_type = information_type
+>>>>>>> MERGE-SOURCE
         return product
 
     def makeProductSeries(self, product=None, name=None, owner=None,

=== modified file 'lib/lp/translations/browser/pofile.py'
=== modified file 'lib/lp/translations/browser/productseries.py'
=== modified file 'lib/lp/translations/browser/sourcepackage.py'
=== modified file 'lib/lp/translations/browser/tests/test_translationmessage_view.py'
--- lib/lp/translations/browser/tests/test_translationmessage_view.py	2012-11-26 08:33:03 +0000
+++ lib/lp/translations/browser/tests/test_translationmessage_view.py	2012-11-30 10:13:01 +0000
@@ -33,6 +33,7 @@
     CurrentTranslationMessagePageView,
     CurrentTranslationMessageView,
     revert_unselected_translations,
+    convert_translationmessage_to_submission,
     )
 from lp.translations.enums import TranslationPermission
 from lp.translations.interfaces.side import ITranslationSideTraitsSet

=== modified file 'lib/lp/translations/browser/translationmessage.py'
=== modified file 'lib/lp/translations/doc/translationsoverview.txt'
--- lib/lp/translations/doc/translationsoverview.txt	2012-11-23 22:10:04 +0000
+++ lib/lp/translations/doc/translationsoverview.txt	2012-11-30 10:13:01 +0000
@@ -177,6 +177,7 @@
     Evolution: 20
     Ubuntu: 24
 
+<<<<<<< TREE
 Private projects are never included.
 
     >>> from lp.app.enums import InformationType
@@ -187,6 +188,18 @@
     Evolution: 20
     Ubuntu: 24
 
+=======
+Private projects are never included.
+
+    >>> from lp.app.enums import InformationType
+    >>> removeSecurityProxy(upstart).information_type = (
+    ...     InformationType.PROPRIETARY)
+    >>> display_pillars(overview.getMostTranslatedPillars())
+    alsa-utils: 22
+    Evolution: 20
+    Ubuntu: 24
+
+>>>>>>> MERGE-SOURCE
 
 Zero karma
 ----------


Follow ups