← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~stub/launchpad/pending-db-changes into lp:launchpad

 

Stuart Bishop has proposed merging lp:~stub/launchpad/pending-db-changes into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~stub/launchpad/pending-db-changes/+merge/108323

= Summary =

Turns out pgstattuple doesn't support GIN indexes, so one of our monitoring scripts fails.

== Proposed fix ==

Skip GIN indexes. Leave a better fix (such as replacing pgstattuple output with guesses) is better left to lp:pgdbr work which will replace this stuff and make it non-LP specific.
-- 
https://code.launchpad.net/~stub/launchpad/pending-db-changes/+merge/108323
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stub/launchpad/pending-db-changes into lp:launchpad.
=== added file 'database/schema/patch-2209-21-4.sql'
--- database/schema/patch-2209-21-4.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2209-21-4.sql	2012-06-01 11:55:34 +0000
@@ -0,0 +1,111 @@
+CREATE OR REPLACE FUNCTION update_database_disk_utilization() RETURNS void
+    LANGUAGE sql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+    INSERT INTO DatabaseDiskUtilization
+    SELECT
+        CURRENT_TIMESTAMP AT TIME ZONE 'UTC',
+        namespace, name,
+        sub_namespace, sub_name,
+        kind,
+        (namespace || '.' ||  name || COALESCE(
+                '/' || sub_namespace || '.' || sub_name, '')) AS sort,
+        (stat).table_len,
+        (stat).tuple_count,
+        (stat).tuple_len,
+        (stat).tuple_percent,
+        (stat).dead_tuple_count,
+        (stat).dead_tuple_len,
+        (stat).dead_tuple_percent,
+        (stat).free_space,
+        (stat).free_percent
+    FROM (
+        -- Tables
+        SELECT
+            pg_namespace.nspname AS namespace,
+            pg_class.relname AS name,
+            NULL AS sub_namespace,
+            NULL AS sub_name,
+            pg_class.relkind AS kind,
+            pgstattuple(pg_class.oid) AS stat
+        FROM pg_class, pg_namespace
+        WHERE
+            pg_class.relnamespace = pg_namespace.oid
+            AND pg_class.relkind = 'r'
+            AND pg_table_is_visible(pg_class.oid)
+
+        UNION ALL
+        
+        -- Indexes
+        SELECT
+            pg_namespace_table.nspname AS namespace,
+            pg_class_table.relname AS name,
+            pg_namespace_index.nspname AS sub_namespace,
+            pg_class_index.relname AS sub_name,
+            pg_class_index.relkind AS kind,
+            pgstattuple(pg_class_index.oid) AS stat
+        FROM
+            pg_namespace AS pg_namespace_table,
+            pg_namespace AS pg_namespace_index,
+            pg_class AS pg_class_table,
+            pg_class AS pg_class_index,
+            pg_index,
+            pg_am
+        WHERE
+            pg_class_index.relkind = 'i'
+            AND pg_am.amname <> 'gin' -- pgstattuple doesn't support GIN
+            AND pg_table_is_visible(pg_class_table.oid)
+            AND pg_class_index.relnamespace = pg_namespace_index.oid
+            AND pg_class_table.relnamespace = pg_namespace_table.oid
+            AND pg_class_index.relam = pg_am.oid
+            AND pg_index.indexrelid = pg_class_index.oid
+            AND pg_index.indrelid = pg_class_table.oid
+
+        UNION ALL
+
+        -- TOAST tables
+        SELECT
+            pg_namespace_table.nspname AS namespace,
+            pg_class_table.relname AS name,
+            pg_namespace_toast.nspname AS sub_namespace,
+            pg_class_toast.relname AS sub_name,
+            pg_class_toast.relkind AS kind,
+            pgstattuple(pg_class_toast.oid) AS stat
+        FROM
+            pg_namespace AS pg_namespace_table,
+            pg_namespace AS pg_namespace_toast,
+            pg_class AS pg_class_table,
+            pg_class AS pg_class_toast
+        WHERE
+            pg_class_toast.relnamespace = pg_namespace_toast.oid
+            AND pg_table_is_visible(pg_class_table.oid)
+            AND pg_class_table.relnamespace = pg_namespace_table.oid
+            AND pg_class_toast.oid = pg_class_table.reltoastrelid
+
+        UNION ALL
+
+        -- TOAST indexes
+        SELECT
+            pg_namespace_table.nspname AS namespace,
+            pg_class_table.relname AS name,
+            pg_namespace_index.nspname AS sub_namespace,
+            pg_class_index.relname AS sub_name,
+            pg_class_index.relkind AS kind,
+            pgstattuple(pg_class_index.oid) AS stat
+        FROM
+            pg_namespace AS pg_namespace_table,
+            pg_namespace AS pg_namespace_index,
+            pg_class AS pg_class_table,
+            pg_class AS pg_class_index,
+            pg_class AS pg_class_toast
+        WHERE
+            pg_class_table.relnamespace = pg_namespace_table.oid
+            AND pg_table_is_visible(pg_class_table.oid)
+            AND pg_class_index.relnamespace = pg_namespace_index.oid
+            AND pg_class_table.reltoastrelid = pg_class_toast.oid
+            AND pg_class_index.oid = pg_class_toast.reltoastidxid
+        ) AS whatever;
+$$;
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2209, 21, 4);
+

=== modified file 'lib/lp/app/browser/tales.py'
--- lib/lp/app/browser/tales.py	2012-05-31 02:20:41 +0000
+++ lib/lp/app/browser/tales.py	2012-06-01 11:55:34 +0000
@@ -666,11 +666,20 @@
             css_classes.add('private')
             css_classes.add('global-notification-visible')
         else:
-            css_classes.add('public')
-        beta = getattr(view, 'beta_features', [])
-        if beta != []:
-            css_classes.add('global-notification-visible')
-        return ' '.join(list(css_classes))
+<<<<<<< TREE
+            css_classes.add('public')
+        beta = getattr(view, 'beta_features', [])
+        if beta != []:
+            css_classes.add('global-notification-visible')
+        return ' '.join(list(css_classes))
+=======
+            css_classes.add('public')
+        beta = getattr(view, 'beta_features', [])
+        if beta != []:
+            css_classes.add('global-notification-visible')
+        return ' '.join(list(css_classes))
+
+>>>>>>> MERGE-SOURCE
 
     def _getSaneBreadcrumbDetail(self, breadcrumb):
         text = breadcrumb.detail

=== modified file 'lib/lp/app/browser/tests/test_launchpad.py'
=== modified file 'lib/lp/app/doc/tales.txt'
=== modified file 'lib/lp/app/templates/banner-macros.pt'
--- lib/lp/app/templates/banner-macros.pt	2012-05-30 13:59:24 +0000
+++ lib/lp/app/templates/banner-macros.pt	2012-06-01 11:55:34 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 <macros
   xmlns="http://www.w3.org/1999/xhtml";
   xmlns:tal="http://xml.zope.org/namespaces/tal";
@@ -54,3 +55,52 @@
 </metal:beta>
 
 </macros>
+=======
+<macros
+  xmlns="http://www.w3.org/1999/xhtml";
+  xmlns:tal="http://xml.zope.org/namespaces/tal";
+  xmlns:metal="http://xml.zope.org/namespaces/metal";
+  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+  i18n:domain="launchpad"
+  tal:omit-tag=""
+>
+
+<metal:privacy define-macro="privacy-banner">
+  <tal:show-banner condition="view/private">
+    <div class="yui3-widget yui3-banner yui3-privacybanner">
+      <div class="yui3-privacybanner-content">
+        <div class="global-notification">
+          <span class="sprite notification-private"></span>
+          <span class="banner-text">The information on this page is private.</span>
+        </div>
+      </div>
+    </div>
+  </tal:show-banner>
+</metal:privacy>
+
+<metal:beta define-macro="beta-banner">
+  <tal:show-banner condition="view/beta_features">
+    <div class="yui3-widget yui3-banner yui3-betabanner">
+      <div class="yui3-betabanner-content">
+        <div class="global-notification">
+          <span class="beta-warning">BETA!</span>
+          <span class="banner-text">
+            Some parts of this page are in beta:&nbsp;
+            <span class="beta-feature">
+              <tal:features
+                repeat="feature view/beta_features">
+                <tal:feature replace="feature/title" />
+                <tal:link condition="feature/url">
+                  (<a tal:attributes="href feature/url" class="info-link">read more</a>)
+                </tal:link>
+              </tal:features>
+            </span>
+          </span>
+        </div>
+      </div>
+    </div>
+  </tal:show-banner>
+</metal:beta>
+
+</macros>
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/archivepublisher/debian_installer.py'
--- lib/lp/archivepublisher/debian_installer.py	2012-05-30 10:25:43 +0000
+++ lib/lp/archivepublisher/debian_installer.py	2012-06-01 11:55:34 +0000
@@ -16,7 +16,22 @@
 import os
 import shutil
 
+<<<<<<< TREE
 from lp.archivepublisher.customupload import CustomUpload
+=======
+from lp.archivepublisher.customupload import (
+    CustomUpload,
+    CustomUploadError,
+    )
+
+
+class DebianInstallerAlreadyExists(CustomUploadError):
+    """A build for this type, architecture, and version already exists."""
+    def __init__(self, arch, version):
+        message = ('installer build %s for architecture %s already exists' %
+                   (arch, version))
+        CustomUploadError.__init__(self, message)
+>>>>>>> MERGE-SOURCE
 
 
 class DebianInstallerUpload(CustomUpload):
@@ -42,13 +57,21 @@
 
     def setTargetDirectory(self, archive_root, tarfile_path, distroseries):
         tarfile_base = os.path.basename(tarfile_path)
+<<<<<<< TREE
         _, self.version, self.arch = tarfile_base.split("_")
         self.arch = self.arch.split(".")[0]
 
+=======
+        components = tarfile_base.split('_')
+        self.version = components[1]
+        self.arch = components[2].split('.')[0]
+
+>>>>>>> MERGE-SOURCE
         self.targetdir = os.path.join(
             archive_root, 'dists', distroseries, 'main',
             'installer-%s' % self.arch)
 
+<<<<<<< TREE
     @classmethod
     def getSeriesKey(cls, tarfile_path):
         try:
@@ -56,6 +79,10 @@
             return arch.split(".")[0]
         except ValueError:
             return None
+=======
+        if os.path.exists(os.path.join(self.targetdir, self.version)):
+            raise DebianInstallerAlreadyExists(self.arch, self.version)
+>>>>>>> MERGE-SOURCE
 
     def extract(self):
         CustomUpload.extract(self)

=== modified file 'lib/lp/archivepublisher/tests/test_debian_installer.py'
--- lib/lp/archivepublisher/tests/test_debian_installer.py	2012-05-30 10:25:43 +0000
+++ lib/lp/archivepublisher/tests/test_debian_installer.py	2012-06-01 11:55:34 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # Copyright 2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
@@ -166,3 +167,152 @@
             "package_1.0.tar.gz"))
         self.assertIsNone(DebianInstallerUpload.getSeriesKey(
             "one_two_three_four_5.tar.gz"))
+=======
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test debian-installer custom uploads.
+
+See also lp.soyuz.tests.test_distroseriesqueue_debian_installer for
+high-level tests of debian-installer upload and queue manipulation.
+"""
+
+import os
+
+from lp.archivepublisher.customupload import CustomUploadBadUmask
+from lp.archivepublisher.debian_installer import (
+    DebianInstallerAlreadyExists,
+    process_debian_installer,
+    )
+from lp.services.tarfile_helpers import LaunchpadWriteTarFile
+from lp.testing import TestCase
+
+
+class TestDebianInstaller(TestCase):
+
+    def setUp(self):
+        super(TestDebianInstaller, self).setUp()
+        self.temp_dir = self.makeTemporaryDirectory()
+        self.suite = "distroseries"
+        # CustomUpload.installFiles requires a umask of 022.
+        old_umask = os.umask(022)
+        self.addCleanup(os.umask, old_umask)
+
+    def openArchive(self):
+        self.version = "20070214ubuntu1"
+        self.arch = "i386"
+        self.path = os.path.join(
+            self.temp_dir,
+            "debian-installer-images_%s_%s.tar.gz" % (self.version, self.arch))
+        self.buffer = open(self.path, "wb")
+        self.archive = LaunchpadWriteTarFile(self.buffer)
+
+    def addFile(self, path, contents):
+        self.archive.add_file(
+            "installer-%s/%s/%s" % (self.arch, self.version, path), contents)
+
+    def addSymlink(self, path, target):
+        self.archive.add_symlink(
+            "installer-%s/%s/%s" % (self.arch, self.version, path), target)
+
+    def process(self):
+        self.archive.close()
+        self.buffer.close()
+        process_debian_installer(self.temp_dir, self.path, self.suite)
+
+    def getInstallerPath(self, versioned_filename=None):
+        installer_path = os.path.join(
+            self.temp_dir, "dists", self.suite, "main",
+            "installer-%s" % self.arch)
+        if versioned_filename is not None:
+            installer_path = os.path.join(
+                installer_path, self.version, versioned_filename)
+        return installer_path
+
+    def test_basic(self):
+        # Processing a simple correct tar file succeeds.
+        self.openArchive()
+        self.addFile("hello", "world")
+        self.process()
+
+    def test_already_exists(self):
+        # If the target directory already exists, processing fails.
+        self.openArchive()
+        os.makedirs(self.getInstallerPath("."))
+        self.assertRaises(DebianInstallerAlreadyExists, self.process)
+
+    def test_bad_umask(self):
+        # The umask must be 022 to avoid incorrect permissions.
+        self.openArchive()
+        self.addFile("dir/file", "foo")
+        os.umask(002)  # cleanup already handled by setUp
+        self.assertRaises(CustomUploadBadUmask, self.process)
+
+    def test_current_symlink(self):
+        # A "current" symlink is created to the last version.
+        self.openArchive()
+        self.addFile("hello", "world")
+        self.process()
+        installer_path = self.getInstallerPath()
+        self.assertContentEqual(
+            [self.version, "current"], os.listdir(installer_path))
+        self.assertEqual(
+            self.version, os.readlink(os.path.join(installer_path, "current")))
+
+    def test_correct_file(self):
+        # Files in the tarball are extracted correctly.
+        self.openArchive()
+        directory = ("images/netboot/ubuntu-installer/i386/"
+                     "pxelinux.cfg.serial-9600")
+        filename = os.path.join(directory, "default")
+        long_filename = os.path.join(
+            directory, "very_very_very_very_very_very_long_filename")
+        self.addFile(filename, "hey")
+        self.addFile(long_filename, "long")
+        self.process()
+        with open(self.getInstallerPath(filename)) as f:
+            self.assertEqual("hey", f.read())
+        with open(self.getInstallerPath(long_filename)) as f:
+            self.assertEqual("long", f.read())
+
+    def test_correct_symlink(self):
+        # Symbolic links in the tarball are extracted correctly.
+        self.openArchive()
+        foo_path = "images/netboot/foo"
+        foo_target = "ubuntu-installer/i386/pxelinux.cfg.serial-9600/default"
+        link_to_dir_path = "images/netboot/link_to_dir"
+        link_to_dir_target = "ubuntu-installer/i386/pxelinux.cfg.serial-9600"
+        self.addSymlink(foo_path, foo_target)
+        self.addSymlink(link_to_dir_path, link_to_dir_target)
+        self.process()
+        self.assertEqual(
+            foo_target, os.readlink(self.getInstallerPath(foo_path)))
+        self.assertEqual(
+            link_to_dir_target,
+            os.path.normpath(os.readlink(
+                self.getInstallerPath(link_to_dir_path))))
+
+    def test_top_level_permissions(self):
+        # Top-level directories are set to mode 0755 (see bug 107068).
+        self.openArchive()
+        self.addFile("hello", "world")
+        self.process()
+        installer_path = self.getInstallerPath()
+        self.assertEqual(0755, os.stat(installer_path).st_mode & 0777)
+        self.assertEqual(
+            0755,
+            os.stat(os.path.join(installer_path, os.pardir)).st_mode & 0777)
+
+    def test_extracted_permissions(self):
+        # Extracted files and directories are set to 0644/0755.
+        self.openArchive()
+        directory = ("images/netboot/ubuntu-installer/i386/"
+                     "pxelinux.cfg.serial-9600")
+        filename = os.path.join(directory, "default")
+        self.addFile(filename, "hey")
+        self.process()
+        self.assertEqual(
+            0644, os.stat(self.getInstallerPath(filename)).st_mode & 0777)
+        self.assertEqual(
+            0755, os.stat(self.getInstallerPath(directory)).st_mode & 0777)
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/archivepublisher/tests/test_dist_upgrader.py'
--- lib/lp/archivepublisher/tests/test_dist_upgrader.py	2012-05-30 10:25:43 +0000
+++ lib/lp/archivepublisher/tests/test_dist_upgrader.py	2012-06-01 11:55:34 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # Copyright 2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
@@ -104,3 +105,91 @@
             "package_1.0.tar.gz"))
         self.assertIsNone(DistUpgraderUpload.getSeriesKey(
             "one_two_three_four_5.tar.gz"))
+=======
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test dist-upgrader custom uploads.
+
+See also lp.soyuz.tests.test_distroseriesqueue_dist_upgrader for high-level
+tests of dist-upgrader upload and queue manipulation.
+"""
+
+import os
+
+from lp.archivepublisher.customupload import CustomUploadBadUmask
+from lp.archivepublisher.dist_upgrader import (
+    DistUpgraderAlreadyExists,
+    DistUpgraderBadVersion,
+    process_dist_upgrader,
+    )
+from lp.services.tarfile_helpers import LaunchpadWriteTarFile
+from lp.testing import TestCase
+
+
+class TestDistUpgrader(TestCase):
+
+    def setUp(self):
+        super(TestDistUpgrader, self).setUp()
+        self.temp_dir = self.makeTemporaryDirectory()
+        self.suite = "distroseries"
+        # CustomUpload.installFiles requires a umask of 022.
+        old_umask = os.umask(022)
+        self.addCleanup(os.umask, old_umask)
+
+    def openArchive(self, version):
+        self.path = os.path.join(
+            self.temp_dir, "dist-upgrader_%s_all.tar.gz" % version)
+        self.buffer = open(self.path, "wb")
+        self.archive = LaunchpadWriteTarFile(self.buffer)
+
+    def process(self):
+        self.archive.close()
+        self.buffer.close()
+        process_dist_upgrader(self.temp_dir, self.path, self.suite)
+
+    def getUpgraderPath(self):
+        return os.path.join(
+            self.temp_dir, "dists", self.suite, "main", "dist-upgrader-all")
+
+    def test_basic(self):
+        # Processing a simple correct tar file works.
+        self.openArchive("20060302.0120")
+        self.archive.add_file("20060302.0120/hello", "world")
+        self.process()
+
+    def test_already_exists(self):
+        # If the target directory already exists, processing fails.
+        self.openArchive("20060302.0120")
+        self.archive.add_file("20060302.0120/hello", "world")
+        os.makedirs(os.path.join(self.getUpgraderPath(), "20060302.0120"))
+        self.assertRaises(DistUpgraderAlreadyExists, self.process)
+
+    def test_bad_umask(self):
+        # The umask must be 022 to avoid incorrect permissions.
+        self.openArchive("20060302.0120")
+        self.archive.add_file("20060302.0120/file", "foo")
+        os.umask(002)  # cleanup already handled by setUp
+        self.assertRaises(CustomUploadBadUmask, self.process)
+
+    def test_current_symlink(self):
+        # A "current" symlink is created to the last version.
+        self.openArchive("20060302.0120")
+        self.archive.add_file("20060302.0120/hello", "world")
+        self.process()
+        upgrader_path = self.getUpgraderPath()
+        self.assertContentEqual(
+            ["20060302.0120", "current"], os.listdir(upgrader_path))
+        self.assertEqual(
+            "20060302.0120",
+            os.readlink(os.path.join(upgrader_path, "current")))
+        self.assertContentEqual(
+            ["hello"],
+            os.listdir(os.path.join(upgrader_path, "20060302.0120")))
+
+    def test_bad_version(self):
+        # Bad versions in the tarball are refused.
+        self.openArchive("20070219.1234")
+        self.archive.add_file("foobar/foobar/dapper.tar.gz", "")
+        self.assertRaises(DistUpgraderBadVersion, self.process)
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py	2012-05-30 00:51:34 +0000
+++ lib/lp/bugs/browser/bugtarget.py	2012-06-01 11:55:34 +0000
@@ -388,6 +388,7 @@
         if self.redirect_ubuntu_filebug:
             pass
         LaunchpadFormView.initialize(self)
+<<<<<<< TREE
         cache = IJSONRequestCache(self.request)
         cache.objects['enable_bugfiling_duplicate_search'] = (
             IProjectGroup.providedBy(self.context)
@@ -408,6 +409,13 @@
             excluded_items=[BugTaskImportance.UNKNOWN])
         cache.objects['bugtask_importance_data'] = bugtask_importance_data
         if (self.extra_data_token is not None and
+=======
+        cache = IJSONRequestCache(self.request)
+        cache.objects['enable_bugfiling_duplicate_search'] = (
+            IProjectGroup.providedBy(self.context)
+            or self.context.enable_bugfiling_duplicate_search)
+        if (self.extra_data_token is not None and
+>>>>>>> MERGE-SOURCE
             not self.extra_data_to_process):
             # self.extra_data has been initialized in publishTraverse().
             if self.extra_data.initial_summary:

=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_filebug.py'
--- lib/lp/bugs/browser/tests/test_bugtarget_filebug.py	2012-05-30 00:51:34 +0000
+++ lib/lp/bugs/browser/tests/test_bugtarget_filebug.py	2012-06-01 11:55:34 +0000
@@ -26,11 +26,18 @@
     BugTaskStatus,
     )
 from lp.bugs.publisher import BugsLayer
+<<<<<<< TREE
 from lp.registry.enums import (
     InformationType,
     PRIVATE_INFORMATION_TYPES,
     PUBLIC_INFORMATION_TYPES,
     )
+=======
+from lp.registry.enums import (
+    InformationType,
+    PRIVATE_INFORMATION_TYPES,
+    )
+>>>>>>> MERGE-SOURCE
 from lp.services.features.testing import FeatureFixture
 from lp.services.webapp.servers import LaunchpadTestRequest
 from lp.testing import (
@@ -477,6 +484,7 @@
             notification.message
             for notification in view.request.response.notifications])
         self.assertIn("Thank you for your bug report.", msg)
+<<<<<<< TREE
 
 
 class TestFileBugGuidelinesRequestCache(TestCaseWithFactory):
@@ -609,3 +617,88 @@
         login_person(user)
         view = create_initialized_view(project, '+filebug', principal=user)
         self._assert_cache_values(view, True)
+=======
+
+
+class TestFileBugGuidelinesRequestCache(TestCaseWithFactory):
+    # Tests to ensure the request cache contains the expected values for
+    # file bug guidelines views.
+
+    layer = DatabaseFunctionalLayer
+
+    def _assert_cache_values(self, view, private_bugs, duplicate_search):
+        cache = IJSONRequestCache(view.request).objects
+        self.assertContentEqual(cache['private_types'], [
+            type.name for type in PRIVATE_INFORMATION_TYPES])
+        self.assertEqual(cache['bug_private_by_default'], private_bugs)
+        self.assertEqual(
+            cache['enable_bugfiling_duplicate_search'], duplicate_search)
+
+    def test_product(self):
+        project = self.factory.makeProduct(official_malone=True)
+        user = self.factory.makePerson()
+        login_person(user)
+        view = create_initialized_view(project,
+            '+filebug-reporting-guidelines', principal=user)
+        self._assert_cache_values(view, False, True)
+
+    def test_product_default_private(self):
+        product = self.factory.makeProduct(official_malone=True)
+        removeSecurityProxy(product).private_bugs = True
+        user = self.factory.makePerson()
+        login_person(user)
+        view = create_initialized_view(product,
+            '+filebug-reporting-guidelines', principal=user)
+        self._assert_cache_values(view, True, True)
+
+    def test_product_no_duplicate_search(self):
+        product = self.factory.makeProduct(official_malone=True)
+        removeSecurityProxy(product).enable_bugfiling_duplicate_search = False
+        user = self.factory.makePerson()
+        login_person(user)
+        view = create_initialized_view(product,
+            '+filebug-reporting-guidelines', principal=user)
+        self._assert_cache_values(view, False, False)
+
+    def test_project_group(self):
+        project = self.factory.makeProject()
+        user = self.factory.makePerson()
+        login_person(user)
+        view = create_initialized_view(project,
+            '+filebug-reporting-guidelines', principal=user)
+        self._assert_cache_values(view, False, True)
+
+
+class TestFileBugRequestCache(TestCaseWithFactory):
+    # Tests to ensure the request cache contains the expected values for
+    # file bug views.
+
+    layer = DatabaseFunctionalLayer
+
+    def _assert_cache_values(self, view, duplicate_search):
+        cache = IJSONRequestCache(view.request).objects
+        self.assertEqual(
+            cache['enable_bugfiling_duplicate_search'], duplicate_search)
+
+    def test_product(self):
+        project = self.factory.makeProduct(official_malone=True)
+        user = self.factory.makePerson()
+        login_person(user)
+        view = create_initialized_view(project, '+filebug', principal=user)
+        self._assert_cache_values(view, True)
+
+    def test_product_no_duplicate_search(self):
+        product = self.factory.makeProduct(official_malone=True)
+        removeSecurityProxy(product).enable_bugfiling_duplicate_search = False
+        user = self.factory.makePerson()
+        login_person(user)
+        view = create_initialized_view(product, '+filebug', principal=user)
+        self._assert_cache_values(view, False)
+
+    def test_project_group(self):
+        project = self.factory.makeProject()
+        user = self.factory.makePerson()
+        login_person(user)
+        view = create_initialized_view(project, '+filebug', principal=user)
+        self._assert_cache_values(view, True)
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
=== modified file 'lib/lp/bugs/javascript/filebug.js'
--- lib/lp/bugs/javascript/filebug.js	2012-05-28 12:34:29 +0000
+++ lib/lp/bugs/javascript/filebug.js	2012-06-01 11:55:34 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 /* Copyright 2012 Canonical Ltd.  This software is licensed under the
  * GNU Affero General Public License version 3 (see the file LICENSE).
  *
@@ -81,3 +82,80 @@
     "base", "node", "event", "node-event-delegate", "lazr.choiceedit",
     "lp.app.banner.privacy", "lp.app.choice",
     "lp.bugs.filebug_dupefinder"]});
+=======
+/* Copyright 2012 Canonical Ltd.  This software is licensed under the
+ * GNU Affero General Public License version 3 (see the file LICENSE).
+ *
+ * Provide functionality for the file bug pages.
+ *
+ * @module bugs
+ * @submodule filebug
+ */
+YUI.add('lp.bugs.filebug', function(Y) {
+
+var namespace = Y.namespace('lp.bugs.filebug');
+
+// For tests.
+var skip_animation;
+
+var setup_filebug = function(skip_anim) {
+    skip_animation = skip_anim;
+    if (LP.cache.enable_bugfiling_duplicate_search) {
+        Y.lp.bugs.filebug_dupefinder.setup_dupe_finder();
+        Y.lp.bugs.filebug_dupefinder.setup_dupes();
+    }
+    var search_button = Y.one(Y.DOM.byId('field.actions.projectgroupsearch'));
+    if (Y.Lang.isValue(search_button )) {
+        search_button.set('value', 'Check again');
+    }
+    if (LP.cache.show_information_type_in_ui) {
+        setup_information_type();
+    } else {
+        setup_security_related();
+    }
+    var filebug_privacy_text = "This report will be private. " +
+        "You can disclose it later.";
+    update_privacy_banner(
+        LP.cache.bug_private_by_default, filebug_privacy_text);
+};
+
+var update_privacy_banner = function(show, banner_text) {
+    var banner = Y.lp.app.banner.privacy.getPrivacyBanner(
+        banner_text, skip_animation);
+    if (show) {
+        banner.show();
+    } else {
+        banner.hide();
+    }
+};
+
+var setup_information_type = function() {
+    var itypes_table = Y.one('.radio-button-widget');
+    itypes_table.delegate('click', function() {
+        var private_type = (Y.Array.indexOf(
+            LP.cache.private_types, this.get('value')) >= 0);
+        update_privacy_banner(private_type);
+    }, "input[name='field.information_type']");
+};
+
+var setup_security_related = function() {
+    var sec = Y.one('[id="field.security_related"]');
+    if (!Y.Lang.isValue(sec)) {
+        return;
+    }
+    var notification_text = "This report will be private " +
+                           "because it is a security " +
+                           "vulnerability. You can " +
+                           "disclose it later.";
+    sec.on('change', function() {
+      var checked = sec.get('checked');
+      update_privacy_banner(checked, notification_text);
+    });
+};
+
+namespace.setup_filebug = setup_filebug;
+
+}, "0.1", {"requires": [
+    "base", "node", "event", "node-event-delegate",
+    "lp.app.banner.privacy", "lp.bugs.filebug_dupefinder"]});
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/bugs/javascript/tests/test_filebug.html'
--- lib/lp/bugs/javascript/tests/test_filebug.html	2012-05-28 12:34:29 +0000
+++ lib/lp/bugs/javascript/tests/test_filebug.html	2012-06-01 11:55:34 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 <!DOCTYPE html>
 <!--
 Copyright 2012 Canonical Ltd.  This software is licensed under the
@@ -117,3 +118,108 @@
         </script>
     </body>
 </html>
+=======
+<!DOCTYPE html>
+<!--
+Copyright 2012 Canonical Ltd.  This software is licensed under the
+GNU Affero General Public License version 3 (see the file LICENSE).
+-->
+
+<html>
+  <head>
+      <title>File Bug View Tests</title>
+
+      <!-- YUI and test setup -->
+      <script type="text/javascript"
+              src="../../../../../build/js/yui/yui/yui.js">
+      </script>
+      <link rel="stylesheet"
+      href="../../../../../build/js/yui/console/assets/console-core.css" />
+      <link rel="stylesheet"
+      href="../../../../../build/js/yui/console/assets/skins/sam/console.css" />
+      <link rel="stylesheet"
+      href="../../../../../build/js/yui/test/assets/skins/sam/test.css" />
+
+      <script type="text/javascript"
+              src="../../../../../build/js/lp/app/testing/testrunner.js"></script>
+
+      <link rel="stylesheet" href="../../../app/javascript/testing/test.css" />
+
+      <!-- Dependencies -->
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/mustache.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/expander.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/formoverlay/formoverlay.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/formwidgets/formwidgets.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/formwidgets/resizing_textarea.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/overlay/overlay.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/anim/anim.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/bugs/filebug_dupefinder.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/effects/effects.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/extras/extras.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/banners/banner.js"></script>
+      <script type="text/javascript"
+          src="../../../../../build/js/lp/app/banners/privacy.js"></script>
+
+      <!-- The module under test. -->
+      <script type="text/javascript" src="../filebug.js"></script>
+
+      <!-- The test suite -->
+      <script type="text/javascript" src="test_filebug.js"></script>
+
+    </head>
+    <body class="yui3-skin-sam">
+        <ul id="suites">
+            <li>lp.bugs.filebug.test</li>
+        </ul>
+        <div class='login-logout'></div>
+        <div id="fixture"></div>
+        <script type="text/x-template" id="privacy-banner-template">
+        <table class="radio-button-widget">
+            <tbody>
+            <tr>
+              <td><input type="radio" value="PUBLIC" name="field.information_type"
+                id="field.information_type.0" checked="checked" class="radioType">
+              </td>
+              <td><label for="field.information_type.0">Public</label></td>
+            </tr>
+            <tr>
+              <td><input type="radio" value="UNEMBARGOEDSECURITY" name="field.information_type"
+                id="field.information_type.1"class="radioType">
+              </td>
+              <td><label for="field.information_type.1">Unembargoed Security</label></td>
+            </tr>
+            <tr>
+              <td><input type="radio" value="EMBARGOEDSECURITY" name="field.information_type"
+                id="field.information_type.2"class="radioType">
+              </td>
+              <td><label for="field.information_type.2">Embargoed Security</label></td>
+            </tr>
+            <tr>
+              <td><input type="radio" value="USERDATA" name="field.information_type"
+                id="field.information_type.3"class="radioType">
+              </td>
+              <td><label for="field.information_type.3">User Data</label></td>
+            </tr>
+            <tr>
+              <td><input type="radio" value="PROPRIETARY" name="field.information_type"
+                id="field.information_type.4"class="radioType">
+              </td>
+              <td><label for="field.information_type.4">Proprietary</label></td>
+            </tr>
+            </tbody>
+        </table>
+        </script>
+    </body>
+</html>
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/bugs/javascript/tests/test_filebug.js'
--- lib/lp/bugs/javascript/tests/test_filebug.js	2012-05-30 21:36:01 +0000
+++ lib/lp/bugs/javascript/tests/test_filebug.js	2012-06-01 11:55:34 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 /* Copyright (c) 2012, Canonical Ltd. All rights reserved. */
 
 YUI.add('lp.bugs.filebug.test', function (Y) {
@@ -166,3 +167,118 @@
         'lp.app.banner.privacy', 'lp.app.choice',
         'lp.bugs.filebug_dupefinder', 'lp.bugs.filebug'
         ]});
+=======
+/* Copyright (c) 2012, Canonical Ltd. All rights reserved. */
+
+YUI.add('lp.bugs.filebug.test', function (Y) {
+
+    var tests = Y.namespace('lp.bugs.filebug.test');
+    tests.suite = new Y.Test.Suite(
+        'lp.bugs.filebug Tests');
+
+    tests.suite.add(new Y.Test.Case({
+        name: 'lp.bugs.filebug_tests',
+
+        setUp: function () {
+            window.LP = {
+                links: {},
+                cache: {
+                    private_types: ['EMBARGOEDSECURITY', 'USERDATA']
+                }
+            };
+            this.fixture = Y.one('#fixture');
+            var banner = Y.Node.create(
+                    Y.one('#privacy-banner-template').getContent());
+            this.fixture.appendChild(banner);
+        },
+
+        tearDown: function () {
+            if (this.fixture !== null) {
+                this.fixture.empty(true);
+            }
+            delete this.fixture;
+            delete window.LP;
+        },
+
+        test_library_exists: function () {
+            Y.Assert.isObject(Y.lp.bugs.filebug,
+                "Could not locate the " +
+                "lp.bugs.filebug module");
+        },
+
+        // Filing a public bug does not show the privacy banner.
+        test_setup_filebug_public: function () {
+            Y.lp.bugs.filebug.setup_filebug(true);
+            var banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNotNull(banner_hidden);
+        },
+
+        // Filing a bug for a project with private bugs shows the privacy
+        // banner.
+        test_setup_filebug_private: function () {
+            window.LP.cache.bug_private_by_default = true;
+            Y.lp.bugs.filebug.setup_filebug(true);
+            var banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNull(banner_hidden);
+            var banner_text = Y.one('.banner-text').get('text');
+            Y.Assert.areEqual(
+                'This report will be private. ' +
+                'You can disclose it later.', banner_text);
+        },
+
+        // Selecting a private info type turns on the privacy banner.
+        test_select_private_info_type: function () {
+            window.LP.cache.show_information_type_in_ui = true;
+            Y.lp.bugs.filebug.setup_filebug(true);
+            var banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNotNull(banner_hidden);
+            Y.one('[id=field.information_type.2]').simulate('click');
+            banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNull(banner_hidden);
+            var banner_text = Y.one('.banner-text').get('text');
+            Y.Assert.areEqual(
+                'This report will be private. ' +
+                'You can disclose it later.', banner_text);
+        },
+
+        // Selecting a public info type turns off the privacy banner.
+        test_select_public_info_type: function () {
+            window.LP.cache.show_information_type_in_ui = true;
+            window.LP.cache.bug_private_by_default = true;
+            Y.lp.bugs.filebug.setup_filebug(true);
+            var banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNull(banner_hidden);
+            Y.one('[id=field.information_type.0]').simulate('click');
+            banner_hidden = Y.one('.yui3-privacybanner-hidden');
+            Y.Assert.isNotNull(banner_hidden);
+        },
+
+        // The dupe finder functionality is setup.
+        test_dupe_finder_setup: function () {
+            window.LP.cache.enable_bugfiling_duplicate_search = true;
+            var orig_setup_dupe_finder =
+                Y.lp.bugs.filebug_dupefinder.setup_dupe_finder;
+            var orig_setup_dupes =
+                Y.lp.bugs.filebug_dupefinder.setup_dupes;
+            var setup_dupe_finder_called = false;
+            var setup_dupes_called = false;
+            Y.lp.bugs.filebug_dupefinder.setup_dupe_finder = function() {
+                setup_dupe_finder_called = true;
+            };
+            Y.lp.bugs.filebug_dupefinder.setup_dupes = function() {
+                setup_dupes_called = true;
+            };
+            Y.lp.bugs.filebug.setup_filebug(true);
+            Y.Assert.isTrue(setup_dupe_finder_called);
+            Y.Assert.isTrue(setup_dupes_called);
+            Y.lp.bugs.filebug_dupefinder.setup_dupes = orig_setup_dupes;
+            Y.lp.bugs.filebug_dupefinder.setup_dupe_finder
+                = orig_setup_dupe_finder;
+        }
+    }));
+
+}, '0.1', {'requires': ['test', 'console', 'event', 'node-event-simulate',
+        'lp.app.banner.privacy', 'lp.bugs.filebug_dupefinder',
+        'lp.bugs.filebug'
+        ]});
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/code/browser/tests/test_branch.py'
=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py	2012-05-31 03:54:13 +0000
+++ lib/lp/code/model/tests/test_branch.py	2012-06-01 11:55:34 +0000
@@ -2337,9 +2337,15 @@
 
     def test_public_stacked_on_private_is_private(self):
         # A public branch stacked on a private branch is private.
+<<<<<<< TREE
         stacked_on = self.factory.makeBranch(
             information_type=InformationType.USERDATA)
         branch = self.factory.makeBranch(stacked_on=stacked_on)
+=======
+        stacked_on = self.factory.makeBranch(private=True)
+        branch = self.factory.makeBranch(
+            stacked_on=stacked_on, private=False)
+>>>>>>> MERGE-SOURCE
         self.assertTrue(branch.private)
         self.assertEqual(
             stacked_on.information_type, branch.information_type)
@@ -2347,10 +2353,16 @@
         self.assertTrue(branch.explicitly_private)
 
     def test_private_stacked_on_public_is_private(self):
+<<<<<<< TREE
         # A private branch stacked on a public branch is private.
         stacked_on = self.factory.makeBranch()
         branch = self.factory.makeBranch(
             stacked_on=stacked_on, information_type=InformationType.USERDATA)
+=======
+        # A private branch stacked on a public branch is private.
+        stacked_on = self.factory.makeBranch(private=False)
+        branch = self.factory.makeBranch(stacked_on=stacked_on, private=True)
+>>>>>>> MERGE-SOURCE
         self.assertTrue(branch.private)
         self.assertNotEqual(
             stacked_on.information_type, branch.information_type)
@@ -2449,6 +2461,7 @@
             branch.setPrivate,
             False, branch.owner)
 
+<<<<<<< TREE
     def test_cannot_transition_with_private_stacked_on(self):
         # If a public branch is stacked on a private branch, it can not
         # change its information_type to public.
@@ -2471,6 +2484,28 @@
         self.assertEqual(
             InformationType.UNEMBARGOEDSECURITY, branch.information_type)
 
+=======
+    def test_cannot_transition_with_private_stacked_on(self):
+        # If a public branch is stacked on a private branch, it can not
+        # change its information_type to public.
+        stacked_on = self.factory.makeBranch(private=True)
+        branch = self.factory.makeBranch(stacked_on=stacked_on)
+        self.assertRaises(
+            BranchCannotChangeInformationType,
+            branch.transitionToInformationType, InformationType.PUBLIC,
+            branch.owner)
+
+    def test_can_transition_with_public_stacked_on(self):
+        # If a private branch is stacked on a public branch, it can change
+        # its information_type.
+        stacked_on = self.factory.makeBranch()
+        branch = self.factory.makeBranch(stacked_on=stacked_on, private=True)
+        branch.transitionToInformationType(
+            InformationType.UNEMBARGOEDSECURITY, branch.owner)
+        self.assertEqual(
+            InformationType.UNEMBARGOEDSECURITY, branch.information_type)
+
+>>>>>>> MERGE-SOURCE
 
 class TestBranchCommitsForDays(TestCaseWithFactory):
     """Tests for `Branch.commitsForDays`."""

=== modified file 'lib/lp/code/model/tests/test_branchlookup.py'
--- lib/lp/code/model/tests/test_branchlookup.py	2012-05-31 03:54:13 +0000
+++ lib/lp/code/model/tests/test_branchlookup.py	2012-06-01 11:55:34 +0000
@@ -148,9 +148,15 @@
         # (this is for anonymous access)
         owner = self.factory.makePerson()
         private_branch = self.factory.makeAnyBranch(
+<<<<<<< TREE
             owner=owner, information_type=InformationType.USERDATA)
         branch = self.factory.makeAnyBranch(
             stacked_on=private_branch, owner=owner)
+=======
+            owner=owner, private=True)
+        branch = self.factory.makeAnyBranch(
+            stacked_on=private_branch, owner=owner)
+>>>>>>> MERGE-SOURCE
         with person_logged_in(owner):
             path = branch_id_alias(branch)
         result = self.branch_set.getIdAndTrailingPath(path)

=== modified file 'lib/lp/code/model/tests/test_branchmergeproposal.py'
=== modified file 'lib/lp/code/model/tests/test_branchvisibility.py'
--- lib/lp/code/model/tests/test_branchvisibility.py	2012-05-31 03:54:13 +0000
+++ lib/lp/code/model/tests/test_branchvisibility.py	2012-06-01 11:55:34 +0000
@@ -138,17 +138,26 @@
         private_owner = self.factory.makePerson()
         test_branches = []
         for x in range(5):
+<<<<<<< TREE
             # We want the first 3 public and the last 3 private.
             information_type = InformationType.PUBLIC
             if x > 2:
                 information_type = InformationType.USERDATA
             branch = self.factory.makeBranch(
                 information_type=information_type)
+=======
+            # We want the first 3 public and the last 3 private.
+            branch = self.factory.makeBranch(private=x > 2)
+>>>>>>> MERGE-SOURCE
             test_branches.append(branch)
         test_branches.append(
+<<<<<<< TREE
             self.factory.makeBranch(
                 owner=private_owner,
                 information_type=InformationType.USERDATA))
+=======
+            self.factory.makeBranch(private=True, owner=private_owner))
+>>>>>>> MERGE-SOURCE
 
         # Anonymous users see just the public branches.
         branch_info = [(branch, branch.private)

=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
=== modified file 'lib/lp/code/xmlrpc/tests/test_branch.py'
=== modified file 'lib/lp/registry/templates/person-upcomingwork.pt'
=== modified file 'lib/lp/registry/tests/test_productjob.py'
=== modified file 'lib/lp/soyuz/model/queue.py'
=== modified file 'lib/lp/soyuz/tests/test_distroseriesqueue_debian_installer.py'
--- lib/lp/soyuz/tests/test_distroseriesqueue_debian_installer.py	2012-05-28 12:50:34 +0000
+++ lib/lp/soyuz/tests/test_distroseriesqueue_debian_installer.py	2012-06-01 11:55:34 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # Copyright 2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
@@ -72,3 +73,79 @@
             CustomUploadAlreadyExists,
             upload.queue_root.customfiles[0].publish, self.logger)
         self.assertEqual("ACCEPTED", upload.queue_root.status.name)
+=======
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test upload and queue manipulation of debian-installer custom uploads.
+
+See also lp.archivepublisher.tests.test_debian_installer for detailed tests
+of debian-installer custom upload extraction.
+"""
+
+import os
+
+import transaction
+
+from lp.archivepublisher.debian_installer import DebianInstallerAlreadyExists
+from lp.archiveuploader.nascentupload import NascentUpload
+from lp.archiveuploader.tests import (
+    datadir,
+    getPolicy,
+    )
+from lp.services.log.logger import DevNullLogger
+from lp.services.mail import stub
+from lp.soyuz.tests.test_publishing import TestNativePublishingBase
+from lp.testing.gpgkeys import import_public_test_keys
+
+
+class TestDistroSeriesQueueDebianInstaller(TestNativePublishingBase):
+
+    def setUp(self):
+        super(TestDistroSeriesQueueDebianInstaller, self).setUp()
+        import_public_test_keys()
+        # CustomUpload.installFiles requires a umask of 022.
+        old_umask = os.umask(022)
+        self.addCleanup(os.umask, old_umask)
+        self.anything_policy = getPolicy(
+            name="anything", distro="ubuntutest", distroseries=None)
+        self.logger = DevNullLogger()
+
+    def uploadTestData(self):
+        upload = NascentUpload.from_changesfile_path(
+            datadir(
+                "debian-installer/"
+                "debian-installer_20070214ubuntu1_i386.changes"),
+            self.anything_policy, self.logger)
+        upload.process()
+        self.assertFalse(upload.is_rejected)
+        self.assertTrue(upload.do_accept())
+        self.assertFalse(upload.rejection_message)
+        return upload
+
+    def test_accepts_correct_upload(self):
+        upload = self.uploadTestData()
+        self.assertEqual(1, upload.queue_root.customfiles.count())
+
+    def test_generates_mail(self):
+        # Two e-mail messages were generated (acceptance and announcement).
+        self.anything_policy.setDistroSeriesAndPocket("hoary-test")
+        self.anything_policy.distroseries.changeslist = "announce@xxxxxxxxxxx"
+        self.uploadTestData()
+        self.assertEqual(2, len(stub.test_emails))
+
+    def test_bad_upload_remains_in_accepted(self):
+        # Bad debian-installer uploads remain in accepted.  Simulate an
+        # on-disk conflict to force an error.
+        upload = self.uploadTestData()
+        # Make sure that we can use the librarian files.
+        transaction.commit()
+        os.makedirs(os.path.join(
+            self.config.distroroot, "ubuntutest", "dists", "hoary-test",
+            "main", "installer-i386", "20070214ubuntu1"))
+        self.assertFalse(upload.queue_root.realiseUpload(self.logger))
+        self.assertRaises(
+            DebianInstallerAlreadyExists,
+            upload.queue_root.customfiles[0].publish, self.logger)
+        self.assertEqual("ACCEPTED", upload.queue_root.status.name)
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/soyuz/tests/test_packageupload.py'
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2012-06-01 02:33:27 +0000
+++ lib/lp/testing/factory.py	2012-06-01 11:55:34 +0000
@@ -1073,8 +1073,13 @@
 
     def makeBranch(self, branch_type=None, owner=None,
                    name=None, product=_DEFAULT, url=_DEFAULT, registrant=None,
+<<<<<<< TREE
                    information_type=None, stacked_on=None,
                    sourcepackage=None, reviewer=None, **optional_branch_args):
+=======
+                   private=None, information_type=None, stacked_on=None,
+                   sourcepackage=None, reviewer=None, **optional_branch_args):
+>>>>>>> MERGE-SOURCE
         """Create and return a new, arbitrary Branch of the given type.
 
         Any parameters for `IBranchNamespace.createBranch` can be specified to
@@ -1120,9 +1125,21 @@
         branch = namespace.createBranch(
             branch_type=branch_type, name=name, registrant=registrant,
             url=url, **optional_branch_args)
-        if information_type is not None:
-            removeSecurityProxy(branch).transitionToInformationType(
-                information_type, registrant, verify_policy=False)
+<<<<<<< TREE
+        if information_type is not None:
+            removeSecurityProxy(branch).transitionToInformationType(
+                information_type, registrant, verify_policy=False)
+=======
+        assert information_type is None or private is None, (
+            "Can not specify both information_type and private")
+        if private is not None:
+            information_type = (
+                InformationType.USERDATA if private else
+                InformationType.PUBLIC)
+        if information_type is not None:
+            removeSecurityProxy(branch).transitionToInformationType(
+                information_type, registrant, verify_policy=False)
+>>>>>>> MERGE-SOURCE
         if stacked_on is not None:
             removeSecurityProxy(branch).branchChanged(
                 removeSecurityProxy(stacked_on).unique_name, 'rev1', None,


Follow ups