← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~rvb/launchpad/db-dds-diffpage-form into lp:launchpad

 

Raphaël Victor Badin has proposed merging lp:~rvb/launchpad/db-dds-diffpage-form into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~rvb/launchpad/db-dds-diffpage-form/+merge/53910


-- 
https://code.launchpad.net/~rvb/launchpad/db-dds-diffpage-form/+merge/53910
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/launchpad/db-dds-diffpage-form into lp:launchpad.
=== modified file 'configs/development/launchpad-lazr.conf'
--- configs/development/launchpad-lazr.conf	2011-02-25 19:27:05 +0000
+++ configs/development/launchpad-lazr.conf	2011-03-17 21:14:13 +0000
@@ -5,10 +5,6 @@
 [meta]
 extends: ../../lib/canonical/config/schema-lazr.conf
 
-[archivepublisher]
-root: /var/tmp/archive
-base_url: http://archive.launchpad.dev/
-
 [branchscanner]
 oops_prefix: BS
 error_dir: /var/tmp/codehosting.test

=== modified file 'configs/testrunner/launchpad-lazr.conf'
--- configs/testrunner/launchpad-lazr.conf	2011-02-25 19:27:05 +0000
+++ configs/testrunner/launchpad-lazr.conf	2011-03-17 21:14:13 +0000
@@ -8,9 +8,6 @@
 [canonical]
 cron_control_url: file:lib/lp/services/scripts/tests/cronscripts.ini
 
-[archivepublisher]
-base_url: http://ftpmaster.internal/
-
 [branchscanner]
 oops_prefix: TSMS
 error_dir: /var/tmp/lperr.test

=== modified file 'database/sampledata/current-dev.sql'
--- database/sampledata/current-dev.sql	2011-02-25 17:46:17 +0000
+++ database/sampledata/current-dev.sql	2011-03-17 21:14:13 +0000
@@ -837,6 +837,12 @@
 
 
 
+
+
+
+
+
+
 SET SESSION AUTHORIZATION DEFAULT;
 
 ALTER TABLE account DISABLE TRIGGER ALL;
@@ -1908,21 +1914,21 @@
 
 ALTER TABLE distribution DISABLE TRIGGER ALL;
 
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (1, 'ubuntu', 'Ubuntu Linux', 'Ubuntu is a new approach to Linux Distribution that includes regular releases, and a simplified single-CD installation system.', 'ubuntulinux.org', 17, 'Ubuntu', 'Ubuntu is a new approach to Linux Distribution that includes regular releases, and a simplified single-CD installation system.', 17, NULL, 1, NULL, true, true, NULL, NULL, 3, 59, NULL, NULL, '2006-10-16 18:31:43.415195', NULL, NULL, NULL, NULL, NULL, true, NULL, true, true, NULL, NULL, NULL, NULL, 20, 20, 20);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (2, 'redhat', 'Redhat Advanced Server', 'Red Hat is a commercial distribution of the GNU/Linux Operating System.', 'redhat.com', 1, 'Red Hat', 'Red Hat is a commercial distribution of the GNU/Linux Operating System.', 1, NULL, 1, NULL, false, false, NULL, 8, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.417928', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (3, 'debian', 'Debian GNU/Linux', 'Debian GNU/Linux is
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (1, 'ubuntu', 'Ubuntu Linux', 'Ubuntu is a new approach to Linux Distribution that includes regular releases, and a simplified single-CD installation system.', 'ubuntulinux.org', 17, 'Ubuntu', 'Ubuntu is a new approach to Linux Distribution that includes regular releases, and a simplified single-CD installation system.', 17, NULL, 1, NULL, true, true, NULL, NULL, 3, 59, NULL, NULL, '2006-10-16 18:31:43.415195', NULL, NULL, NULL, NULL, NULL, true, NULL, true, true, NULL, NULL, NULL, NULL, 20, 20, 20, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (2, 'redhat', 'Redhat Advanced Server', 'Red Hat is a commercial distribution of the GNU/Linux Operating System.', 'redhat.com', 1, 'Red Hat', 'Red Hat is a commercial distribution of the GNU/Linux Operating System.', 1, NULL, 1, NULL, false, false, NULL, 8, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.417928', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (3, 'debian', 'Debian GNU/Linux', 'Debian GNU/Linux is
 a non commercial distribution of a GNU/Linux Operating System for many
 platforms.', 'debian.org', 1, 'Debian', 'Debian GNU/Linux is
 a non commercial distribution of a GNU/Linux Operating System for many
-platforms.', 1, NULL, 1, NULL, false, false, NULL, NULL, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.418942', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (4, 'gentoo', 'The Gentoo Linux', 'Gentoo is a very
+platforms.', 1, NULL, 1, NULL, false, false, NULL, NULL, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.418942', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (4, 'gentoo', 'The Gentoo Linux', 'Gentoo is a very
 customizeable GNU/Linux Distribution that is designed to let you build every
-single package yourself, with your own preferences.', 'gentoo.org', 1, 'Gentoo', 'Gentoo is a very customizeable GNU/Linux Distribution that is designed to let you build every single package yourself, with your own preferences.', 1, NULL, 1, NULL, true, false, NULL, NULL, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.41974', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (5, 'kubuntu', 'Kubuntu - Free KDE-based Linux', 'Kubuntu is an entirely free Linux distribution that uses the K Desktop
+single package yourself, with your own preferences.', 'gentoo.org', 1, 'Gentoo', 'Gentoo is a very customizeable GNU/Linux Distribution that is designed to let you build every single package yourself, with your own preferences.', 1, NULL, 1, NULL, true, false, NULL, NULL, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.41974', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (5, 'kubuntu', 'Kubuntu - Free KDE-based Linux', 'Kubuntu is an entirely free Linux distribution that uses the K Desktop
 Environment as its default desktop after install.', 'kubuntu.org', 1, 'Kubuntu', 'Kubuntu is an entirely free Linux distribution that uses the K Desktop
-Environment as its default desktop after install.', 1, NULL, 1, NULL, false, false, NULL, 8, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.420551', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (7, 'guadalinex', 'GuadaLinex: Linux for Andalucia', 'GuadaLinex is based on Ubuntu and adds full support for applications specific to the local environment in Andalucia.', 'guadalinex.es', 4, 'GuadaLinex', 'The GuadaLinex team produces a high quality linux for the Andalucian marketplace.', 32, NULL, 1, NULL, false, false, NULL, NULL, NULL, 4, NULL, NULL, '2006-10-16 18:31:43.421329', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (8, 'ubuntutest', 'Ubuntu Test', 'Ubuntu Test', 'ubuntulinux.org', 17, 'ubuntutest', 'Ubuntu Test summary', 17, NULL, 1, NULL, false, false, NULL, NULL, NULL, 17, NULL, NULL, '2006-10-16 18:31:43.422162', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
+Environment as its default desktop after install.', 1, NULL, 1, NULL, false, false, NULL, 8, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.420551', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (7, 'guadalinex', 'GuadaLinex: Linux for Andalucia', 'GuadaLinex is based on Ubuntu and adds full support for applications specific to the local environment in Andalucia.', 'guadalinex.es', 4, 'GuadaLinex', 'The GuadaLinex team produces a high quality linux for the Andalucian marketplace.', 32, NULL, 1, NULL, false, false, NULL, NULL, NULL, 4, NULL, NULL, '2006-10-16 18:31:43.421329', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (8, 'ubuntutest', 'Ubuntu Test', 'Ubuntu Test', 'ubuntulinux.org', 17, 'ubuntutest', 'Ubuntu Test summary', 17, NULL, 1, NULL, false, false, NULL, NULL, NULL, 17, NULL, NULL, '2006-10-16 18:31:43.422162', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
 
 
 ALTER TABLE distribution ENABLE TRIGGER ALL;
@@ -2891,54 +2897,54 @@
 
 ALTER TABLE message DISABLE TRIGGER ALL;
 
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (1, '2004-09-24 20:58:04.684057', 'PEBCAK', 16, NULL, NULL, 'foo@xxxxxxxxxxx-332342--1231', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (3, '2004-09-24 21:17:17.153792', 'Reproduced on AIX', 12, NULL, NULL, 'sdsdfsfd', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (4, '2004-09-24 21:24:03.922564', 'Re: Reproduced on AIX', 12, NULL, NULL, 'sdfssfdfsd', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (5, '2004-09-24 21:29:27.407354', 'Fantastic idea, I''d really like to see this', 12, NULL, NULL, 'dxssdfsdgf', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (6, '2004-09-24 21:35:20.125564', 'Strange bug with duplicate messages.', 12, NULL, NULL, 'sdfsfwew', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (7, '2005-01-14 17:20:12.820778', 'Reflow problems with complex page layouts', 12, NULL, NULL, '<20050114172012.6687.51124.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (8, '2005-01-14 17:27:03.702622', 'Firefox install instructions should be complete', 12, NULL, NULL, '<20050114172703.6687.71983.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (9, '2005-01-14 17:35:39.548665', 'Firefox crashes when Save As dialog for a nonexistent window is closed', 12, NULL, NULL, '<20050114173539.6687.81610.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (10, '2004-10-05 00:00:00', 'Re: Bug Title Test', 12, NULL, NULL, '<20050831114528.7616.78129.malone@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (11, '2005-10-14 15:12:29.602117', 'A test bug', 16, NULL, NULL, '<20051014151229.28962.1536.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (12, '2005-10-14 12:25:21.508923', 'Re: Newly installed plug-in doesn''t seem to be used', 16, NULL, NULL, '<20051014122521.14276.39260.lptickets@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (13, '2005-10-14 13:28:11.554476', 'Re: Slow system', 12, NULL, NULL, '<20051014132811.14276.65873.lptickets@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (14, '2005-10-28 09:10:17.13237', 'Printing doesn''t work', 12, NULL, 3, '<20051028091017.6690.9505.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (15, '2006-02-23 16:42:14.080227', 'Thunderbird crashes', 16, NULL, 1, '<20060223164214.9126.7558.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (16, '2006-06-16 17:12:54', 'Unicodeâ„¢', 16, NULL, NULL, '<20060616141252.22134.71562@localhost.localdomain>', NULL, 51);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (17, '2006-02-22 19:42:21.890299', 'another test bug', 16, NULL, 1, '<20060222194221.25842.69665.malonedeb@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (18, '2006-07-20 20:48:24.975495', 'Re: Continue playing after shutdown', 16, NULL, NULL, '<20060720204825.13277.37433.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (19, '2006-07-20 20:49:47.551344', 'Re: mailto: problem in webpage', 16, NULL, NULL, '<20060720204947.13277.79684.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (20, '2006-07-20 20:52:07.054216', 'Re: Installation of Java Runtime Environment for Mozilla', 16, NULL, NULL, '<20060720205207.13277.68582.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (21, '2006-07-20 20:53:53.684848', 'Re: Play DVDs in Totem', 16, NULL, NULL, '<20060720205354.13277.37000.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (22, '2006-07-20 20:56:35.442839', 'Re: mailto: problem in webpage', 12, NULL, NULL, '<20060720205635.13277.87295.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (23, '2006-07-20 23:11:24.975495', 'Re: Continue playing after shutdown', 12, NULL, NULL, '<20061201222020.597.97888.lptickets@xxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (24, '2007-03-15 20:33:56.67893', 'Make Jokosher use autoaudiosink', 26, NULL, NULL, '<20070315203356.12919.76581.malonedeb@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (25, '2007-03-15 20:34:26.518114', 'Re: Make Jokosher use autoaudiosink', 50, NULL, NULL, '<20070315203426.12919.54628.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (26, '2007-03-15 20:35:10.133383', 'Re: Make Jokosher use autoaudiosink', 66, NULL, NULL, '<20070315203510.12919.22697.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (27, '2007-03-15 20:36:01.779544', 'Autoaudiosink is no longer under development', 63, NULL, NULL, '<20070315203601.12919.29640.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (28, '2007-03-15 20:36:57.133832', 'Re: Autoaudiosink is no longer under development', 27, NULL, NULL, '<20070315203657.12919.48585.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (29, '2007-03-15 20:37:27.991571', 'This is a really new title', 33, NULL, NULL, '<20070315203728.12919.76787.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (30, '2007-03-15 20:37:51.544376', 'Re: Make Jokosher use autoaudiosink', 3, NULL, NULL, '<20070315203751.12919.49072.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (31, '2007-03-15 20:41:18.635493', 'Copy, Cut and Delete operations should work on selections', 8, NULL, NULL, '<20070315204118.14326.61124.malonedeb@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (32, '2007-03-15 20:41:42.154264', 'Re: Copy, Cut and Delete operations should work on selections', 16, NULL, NULL, '<20070315204142.14326.82988.launchpad@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (33, '2007-03-15 20:43:14.981111', 'Re: Copy, Cut and Delete operations should work on selections', 45, NULL, NULL, '<20070315204315.14326.75272.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (34, '2007-03-15 20:45:15.852052', 'Re: Copy, Cut and Delete operations should work on selections', 13, NULL, NULL, '<20070315204515.14326.38817.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (35, '2007-03-15 20:45:51.817826', 'Re: Copy, Cut and Delete operations should work on selections', 9, NULL, NULL, '<20070315204551.14326.36994.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (36, '2007-03-15 20:46:49.83307', 'Re: Copy, Cut and Delete operations should work on selections', 6, NULL, NULL, '<20070315204649.14326.69581.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (37, '2007-07-27 20:00:58.299796', 'Launchpad CSS and JS is not testible', 12, NULL, NULL, '<20070727200058.25131.76173.malonedeb@autumn.annrky-sinzui.local>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (38, '2007-07-27 20:29:46.25854', 'Re: Launchpad CSS and JS is not testible', 12, NULL, NULL, '<20070727202946.25131.16206.malone@autumn.annrky-sinzui.local>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (39, '2007-08-09 11:39:16.836856', 'jokosher exposes personal details in its actions portlet', 63, NULL, NULL, '<20070809113916.26819.83859.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (40, '2004-12-18 16:30:19.103679', 'Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163019.18924.87555.malonedeb@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (41, '2007-12-18 16:30:47.889614', 'Re: Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163048.18924.13348.launchpad@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (42, '2007-12-18 16:30:47.889614', 'Re: Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163048.18924.86681.launchpad@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (43, '2007-12-18 16:31:34.790641', 'Re: Nonsensical bugs are useless', 62, NULL, NULL, '<20071218163134.18996.53651.launchpad@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (44, '2005-05-13 16:37:37', 'gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, NULL, NULL, '<4284D7D1.6010208@xxxxxx>', NULL, 75);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (45, '2005-05-17 18:54:29', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243615, 44, NULL, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', NULL, 76);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (46, '2005-05-17 19:24:25', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, 45, NULL, '<428A44E9.6090802@xxxxxx>', NULL, 77);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (47, '2005-05-17 20:20:44', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243615, 46, NULL, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', NULL, 78);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (48, '2005-06-17 14:00:11', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243616, 46, NULL, '<20050617140011.GA15638@xxxxxxxxx>', NULL, 79);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (49, '2005-06-25 10:13:10', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, NULL, NULL, '<42BD2E36.9090809@xxxxxx>', NULL, 81);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (1, '2004-09-24 20:58:04.684057', 'PEBCAK', 16, NULL, NULL, 'foo@xxxxxxxxxxx-332342--1231', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (3, '2004-09-24 21:17:17.153792', 'Reproduced on AIX', 12, NULL, NULL, 'sdsdfsfd', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (4, '2004-09-24 21:24:03.922564', 'Re: Reproduced on AIX', 12, NULL, NULL, 'sdfssfdfsd', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (5, '2004-09-24 21:29:27.407354', 'Fantastic idea, I''d really like to see this', 12, NULL, NULL, 'dxssdfsdgf', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (6, '2004-09-24 21:35:20.125564', 'Strange bug with duplicate messages.', 12, NULL, NULL, 'sdfsfwew', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (7, '2005-01-14 17:20:12.820778', 'Reflow problems with complex page layouts', 12, NULL, NULL, '<20050114172012.6687.51124.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (8, '2005-01-14 17:27:03.702622', 'Firefox install instructions should be complete', 12, NULL, NULL, '<20050114172703.6687.71983.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (9, '2005-01-14 17:35:39.548665', 'Firefox crashes when Save As dialog for a nonexistent window is closed', 12, NULL, NULL, '<20050114173539.6687.81610.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (10, '2004-10-05 00:00:00', 'Re: Bug Title Test', 12, NULL, NULL, '<20050831114528.7616.78129.malone@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (11, '2005-10-14 15:12:29.602117', 'A test bug', 16, NULL, NULL, '<20051014151229.28962.1536.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (12, '2005-10-14 12:25:21.508923', 'Re: Newly installed plug-in doesn''t seem to be used', 16, NULL, NULL, '<20051014122521.14276.39260.lptickets@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (13, '2005-10-14 13:28:11.554476', 'Re: Slow system', 12, NULL, NULL, '<20051014132811.14276.65873.lptickets@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (14, '2005-10-28 09:10:17.13237', 'Printing doesn''t work', 12, NULL, 3, '<20051028091017.6690.9505.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (15, '2006-02-23 16:42:14.080227', 'Thunderbird crashes', 16, NULL, 1, '<20060223164214.9126.7558.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (16, '2006-06-16 17:12:54', 'Unicodeâ„¢', 16, NULL, NULL, '<20060616141252.22134.71562@localhost.localdomain>', NULL, 51, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (17, '2006-02-22 19:42:21.890299', 'another test bug', 16, NULL, 1, '<20060222194221.25842.69665.malonedeb@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (18, '2006-07-20 20:48:24.975495', 'Re: Continue playing after shutdown', 16, NULL, NULL, '<20060720204825.13277.37433.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (19, '2006-07-20 20:49:47.551344', 'Re: mailto: problem in webpage', 16, NULL, NULL, '<20060720204947.13277.79684.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (20, '2006-07-20 20:52:07.054216', 'Re: Installation of Java Runtime Environment for Mozilla', 16, NULL, NULL, '<20060720205207.13277.68582.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (21, '2006-07-20 20:53:53.684848', 'Re: Play DVDs in Totem', 16, NULL, NULL, '<20060720205354.13277.37000.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (22, '2006-07-20 20:56:35.442839', 'Re: mailto: problem in webpage', 12, NULL, NULL, '<20060720205635.13277.87295.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (23, '2006-07-20 23:11:24.975495', 'Re: Continue playing after shutdown', 12, NULL, NULL, '<20061201222020.597.97888.lptickets@xxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (24, '2007-03-15 20:33:56.67893', 'Make Jokosher use autoaudiosink', 26, NULL, NULL, '<20070315203356.12919.76581.malonedeb@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (25, '2007-03-15 20:34:26.518114', 'Re: Make Jokosher use autoaudiosink', 50, NULL, NULL, '<20070315203426.12919.54628.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (26, '2007-03-15 20:35:10.133383', 'Re: Make Jokosher use autoaudiosink', 66, NULL, NULL, '<20070315203510.12919.22697.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (27, '2007-03-15 20:36:01.779544', 'Autoaudiosink is no longer under development', 63, NULL, NULL, '<20070315203601.12919.29640.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (28, '2007-03-15 20:36:57.133832', 'Re: Autoaudiosink is no longer under development', 27, NULL, NULL, '<20070315203657.12919.48585.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (29, '2007-03-15 20:37:27.991571', 'This is a really new title', 33, NULL, NULL, '<20070315203728.12919.76787.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (30, '2007-03-15 20:37:51.544376', 'Re: Make Jokosher use autoaudiosink', 3, NULL, NULL, '<20070315203751.12919.49072.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (31, '2007-03-15 20:41:18.635493', 'Copy, Cut and Delete operations should work on selections', 8, NULL, NULL, '<20070315204118.14326.61124.malonedeb@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (32, '2007-03-15 20:41:42.154264', 'Re: Copy, Cut and Delete operations should work on selections', 16, NULL, NULL, '<20070315204142.14326.82988.launchpad@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (33, '2007-03-15 20:43:14.981111', 'Re: Copy, Cut and Delete operations should work on selections', 45, NULL, NULL, '<20070315204315.14326.75272.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (34, '2007-03-15 20:45:15.852052', 'Re: Copy, Cut and Delete operations should work on selections', 13, NULL, NULL, '<20070315204515.14326.38817.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (35, '2007-03-15 20:45:51.817826', 'Re: Copy, Cut and Delete operations should work on selections', 9, NULL, NULL, '<20070315204551.14326.36994.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (36, '2007-03-15 20:46:49.83307', 'Re: Copy, Cut and Delete operations should work on selections', 6, NULL, NULL, '<20070315204649.14326.69581.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (37, '2007-07-27 20:00:58.299796', 'Launchpad CSS and JS is not testible', 12, NULL, NULL, '<20070727200058.25131.76173.malonedeb@autumn.annrky-sinzui.local>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (38, '2007-07-27 20:29:46.25854', 'Re: Launchpad CSS and JS is not testible', 12, NULL, NULL, '<20070727202946.25131.16206.malone@autumn.annrky-sinzui.local>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (39, '2007-08-09 11:39:16.836856', 'jokosher exposes personal details in its actions portlet', 63, NULL, NULL, '<20070809113916.26819.83859.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (40, '2004-12-18 16:30:19.103679', 'Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163019.18924.87555.malonedeb@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (41, '2007-12-18 16:30:47.889614', 'Re: Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163048.18924.13348.launchpad@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (42, '2007-12-18 16:30:47.889614', 'Re: Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163048.18924.86681.launchpad@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (43, '2007-12-18 16:31:34.790641', 'Re: Nonsensical bugs are useless', 62, NULL, NULL, '<20071218163134.18996.53651.launchpad@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (44, '2005-05-13 16:37:37', 'gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, NULL, NULL, '<4284D7D1.6010208@xxxxxx>', NULL, 75, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (45, '2005-05-17 18:54:29', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243615, 44, NULL, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', NULL, 76, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (46, '2005-05-17 19:24:25', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, 45, NULL, '<428A44E9.6090802@xxxxxx>', NULL, 77, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (47, '2005-05-17 20:20:44', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243615, 46, NULL, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', NULL, 78, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (48, '2005-06-17 14:00:11', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243616, 46, NULL, '<20050617140011.GA15638@xxxxxxxxx>', NULL, 79, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (49, '2005-06-25 10:13:10', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, NULL, NULL, '<42BD2E36.9090809@xxxxxx>', NULL, 81, true);
 
 
 ALTER TABLE message ENABLE TRIGGER ALL;
@@ -3336,42 +3342,42 @@
 
 ALTER TABLE bugmessage DISABLE TRIGGER ALL;
 
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (1, 2, 1, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (2, 1, 3, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (3, 1, 4, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (4, 2, 5, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (5, 2, 6, NULL, NULL, true, 2);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (6, 4, 7, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (7, 5, 8, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (8, 6, 9, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (9, 3, 10, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (10, 7, 11, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (11, 8, 14, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (12, 9, 15, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (13, 10, 17, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (14, 10, 16, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (15, 11, 24, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (16, 11, 25, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (17, 11, 26, NULL, NULL, true, 2);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (18, 11, 27, NULL, NULL, true, 3);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (19, 11, 28, NULL, NULL, true, 4);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (20, 11, 29, NULL, NULL, true, 5);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (21, 11, 30, NULL, NULL, true, 6);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (22, 12, 31, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (23, 12, 33, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (24, 12, 34, NULL, NULL, true, 2);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (25, 12, 35, NULL, NULL, true, 3);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (26, 12, 36, NULL, NULL, true, 4);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (27, 13, 37, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (28, 13, 38, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (29, 14, 39, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (30, 15, 40, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@xxxxxx>', true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', true, 2);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (33, 15, 46, 11, '<428A44E9.6090802@xxxxxx>', true, 3);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', true, 4);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@xxxxxxxxx>', true, 5);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@xxxxxx>', true, 6);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (1, 2, 1, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (2, 1, 3, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (3, 1, 4, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (4, 2, 5, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (5, 2, 6, NULL, NULL, 2);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (6, 4, 7, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (7, 5, 8, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (8, 6, 9, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (9, 3, 10, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (10, 7, 11, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (11, 8, 14, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (12, 9, 15, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (13, 10, 17, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (14, 10, 16, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (15, 11, 24, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (16, 11, 25, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (17, 11, 26, NULL, NULL, 2);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (18, 11, 27, NULL, NULL, 3);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (19, 11, 28, NULL, NULL, 4);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (20, 11, 29, NULL, NULL, 5);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (21, 11, 30, NULL, NULL, 6);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (22, 12, 31, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (23, 12, 33, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (24, 12, 34, NULL, NULL, 2);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (25, 12, 35, NULL, NULL, 3);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (26, 12, 36, NULL, NULL, 4);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (27, 13, 37, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (28, 13, 38, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (29, 14, 39, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (30, 15, 40, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@xxxxxx>', 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', 2);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (33, 15, 46, 11, '<428A44E9.6090802@xxxxxx>', 3);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', 4);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@xxxxxxxxx>', 5);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@xxxxxx>', 6);
 
 
 ALTER TABLE bugmessage ENABLE TRIGGER ALL;
@@ -3995,6 +4001,13 @@
 ALTER TABLE databasecpustats ENABLE TRIGGER ALL;
 
 
+ALTER TABLE databasediskutilization DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE databasediskutilization ENABLE TRIGGER ALL;
+
+
 ALTER TABLE databasereplicationlag DISABLE TRIGGER ALL;
 
 
@@ -4384,6 +4397,20 @@
 ALTER TABLE featureflag ENABLE TRIGGER ALL;
 
 
+ALTER TABLE featureflagchangelogentry DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE featureflagchangelogentry ENABLE TRIGGER ALL;
+
+
+ALTER TABLE featureflagchangelogentry DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE featureflagchangelogentry ENABLE TRIGGER ALL;
+
+
 ALTER TABLE flatpackagesetinclusion DISABLE TRIGGER ALL;
 
 
@@ -9898,6 +9925,15 @@
 ALTER TABLE projectrelationship ENABLE TRIGGER ALL;
 
 
+ALTER TABLE publisherconfig DISABLE TRIGGER ALL;
+
+INSERT INTO publisherconfig (id, distribution, root_dir, base_url, copy_base_url) VALUES (1, 1, '/var/tmp/archive', 'http://archive.launchpad.dev/', 'http://rebuild-test.internal/');
+INSERT INTO publisherconfig (id, distribution, root_dir, base_url, copy_base_url) VALUES (2, 8, '/var/tmp/archive', 'http://archive.launchpad.dev/', 'http://rebuild-test.internal/');
+
+
+ALTER TABLE publisherconfig ENABLE TRIGGER ALL;
+
+
 ALTER TABLE pushmirroraccess DISABLE TRIGGER ALL;
 
 

=== modified file 'database/sampledata/current.sql'
--- database/sampledata/current.sql	2011-02-25 17:46:17 +0000
+++ database/sampledata/current.sql	2011-03-17 21:14:13 +0000
@@ -837,6 +837,12 @@
 
 
 
+
+
+
+
+
+
 SET SESSION AUTHORIZATION DEFAULT;
 
 ALTER TABLE account DISABLE TRIGGER ALL;
@@ -1908,21 +1914,21 @@
 
 ALTER TABLE distribution DISABLE TRIGGER ALL;
 
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (1, 'ubuntu', 'Ubuntu Linux', 'Ubuntu is a new approach to Linux Distribution that includes regular releases, and a simplified single-CD installation system.', 'ubuntulinux.org', 17, 'Ubuntu', 'Ubuntu is a new approach to Linux Distribution that includes regular releases, and a simplified single-CD installation system.', 17, NULL, 1, NULL, true, true, NULL, NULL, 3, 59, NULL, NULL, '2006-10-16 18:31:43.415195', NULL, NULL, NULL, NULL, NULL, true, NULL, true, true, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (2, 'redhat', 'Redhat Advanced Server', 'Red Hat is a commercial distribution of the GNU/Linux Operating System.', 'redhat.com', 1, 'Red Hat', 'Red Hat is a commercial distribution of the GNU/Linux Operating System.', 1, NULL, 1, NULL, false, false, NULL, 8, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.417928', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (3, 'debian', 'Debian GNU/Linux', 'Debian GNU/Linux is
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (1, 'ubuntu', 'Ubuntu Linux', 'Ubuntu is a new approach to Linux Distribution that includes regular releases, and a simplified single-CD installation system.', 'ubuntulinux.org', 17, 'Ubuntu', 'Ubuntu is a new approach to Linux Distribution that includes regular releases, and a simplified single-CD installation system.', 17, NULL, 1, NULL, true, true, NULL, NULL, 3, 59, NULL, NULL, '2006-10-16 18:31:43.415195', NULL, NULL, NULL, NULL, NULL, true, NULL, true, true, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (2, 'redhat', 'Redhat Advanced Server', 'Red Hat is a commercial distribution of the GNU/Linux Operating System.', 'redhat.com', 1, 'Red Hat', 'Red Hat is a commercial distribution of the GNU/Linux Operating System.', 1, NULL, 1, NULL, false, false, NULL, 8, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.417928', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (3, 'debian', 'Debian GNU/Linux', 'Debian GNU/Linux is
 a non commercial distribution of a GNU/Linux Operating System for many
 platforms.', 'debian.org', 1, 'Debian', 'Debian GNU/Linux is
 a non commercial distribution of a GNU/Linux Operating System for many
-platforms.', 1, NULL, 1, NULL, false, false, NULL, NULL, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.418942', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (4, 'gentoo', 'The Gentoo Linux', 'Gentoo is a very
+platforms.', 1, NULL, 1, NULL, false, false, NULL, NULL, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.418942', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (4, 'gentoo', 'The Gentoo Linux', 'Gentoo is a very
 customizeable GNU/Linux Distribution that is designed to let you build every
-single package yourself, with your own preferences.', 'gentoo.org', 1, 'Gentoo', 'Gentoo is a very customizeable GNU/Linux Distribution that is designed to let you build every single package yourself, with your own preferences.', 1, NULL, 1, NULL, true, false, NULL, NULL, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.41974', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (5, 'kubuntu', 'Kubuntu - Free KDE-based Linux', 'Kubuntu is an entirely free Linux distribution that uses the K Desktop
+single package yourself, with your own preferences.', 'gentoo.org', 1, 'Gentoo', 'Gentoo is a very customizeable GNU/Linux Distribution that is designed to let you build every single package yourself, with your own preferences.', 1, NULL, 1, NULL, true, false, NULL, NULL, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.41974', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (5, 'kubuntu', 'Kubuntu - Free KDE-based Linux', 'Kubuntu is an entirely free Linux distribution that uses the K Desktop
 Environment as its default desktop after install.', 'kubuntu.org', 1, 'Kubuntu', 'Kubuntu is an entirely free Linux distribution that uses the K Desktop
-Environment as its default desktop after install.', 1, NULL, 1, NULL, false, false, NULL, 8, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.420551', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (7, 'guadalinex', 'GuadaLinex: Linux for Andalucia', 'GuadaLinex is based on Ubuntu and adds full support for applications specific to the local environment in Andalucia.', 'guadalinex.es', 4, 'GuadaLinex', 'The GuadaLinex team produces a high quality linux for the Andalucian marketplace.', 32, NULL, 1, NULL, false, false, NULL, NULL, NULL, 4, NULL, NULL, '2006-10-16 18:31:43.421329', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
-INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage) VALUES (8, 'ubuntutest', 'Ubuntu Test', 'Ubuntu Test', 'ubuntulinux.org', 17, 'ubuntutest', 'Ubuntu Test summary', 17, NULL, 1, NULL, false, false, NULL, NULL, NULL, 17, NULL, NULL, '2006-10-16 18:31:43.422162', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10);
+Environment as its default desktop after install.', 1, NULL, 1, NULL, false, false, NULL, 8, NULL, 1, NULL, NULL, '2006-10-16 18:31:43.420551', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (7, 'guadalinex', 'GuadaLinex: Linux for Andalucia', 'GuadaLinex is based on Ubuntu and adds full support for applications specific to the local environment in Andalucia.', 'guadalinex.es', 4, 'GuadaLinex', 'The GuadaLinex team produces a high quality linux for the Andalucian marketplace.', 32, NULL, 1, NULL, false, false, NULL, NULL, NULL, 4, NULL, NULL, '2006-10-16 18:31:43.421329', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
+INSERT INTO distribution (id, name, title, description, domainname, owner, displayname, summary, members, translationgroup, translationpermission, bug_supervisor, official_malone, official_rosetta, security_contact, driver, translation_focus, mirror_admin, upload_admin, upload_sender, date_created, homepage_content, icon, mugshot, logo, fti, official_answers, language_pack_admin, official_blueprints, enable_bug_expiration, bug_reporting_guidelines, reviewer_whiteboard, max_bug_heat, bug_reported_acknowledgement, answers_usage, blueprints_usage, translations_usage, registrant) VALUES (8, 'ubuntutest', 'Ubuntu Test', 'Ubuntu Test', 'ubuntulinux.org', 17, 'ubuntutest', 'Ubuntu Test summary', 17, NULL, 1, NULL, false, false, NULL, NULL, NULL, 17, NULL, NULL, '2006-10-16 18:31:43.422162', NULL, NULL, NULL, NULL, NULL, false, NULL, false, false, NULL, NULL, NULL, NULL, 10, 10, 10, 60);
 
 
 ALTER TABLE distribution ENABLE TRIGGER ALL;
@@ -2891,54 +2897,54 @@
 
 ALTER TABLE message DISABLE TRIGGER ALL;
 
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (1, '2004-09-24 20:58:04.684057', 'PEBCAK', 16, NULL, NULL, 'foo@xxxxxxxxxxx-332342--1231', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (3, '2004-09-24 21:17:17.153792', 'Reproduced on AIX', 12, NULL, NULL, 'sdsdfsfd', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (4, '2004-09-24 21:24:03.922564', 'Re: Reproduced on AIX', 12, NULL, NULL, 'sdfssfdfsd', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (5, '2004-09-24 21:29:27.407354', 'Fantastic idea, I''d really like to see this', 12, NULL, NULL, 'dxssdfsdgf', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (6, '2004-09-24 21:35:20.125564', 'Strange bug with duplicate messages.', 12, NULL, NULL, 'sdfsfwew', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (7, '2005-01-14 17:20:12.820778', 'Reflow problems with complex page layouts', 12, NULL, NULL, '<20050114172012.6687.51124.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (8, '2005-01-14 17:27:03.702622', 'Firefox install instructions should be complete', 12, NULL, NULL, '<20050114172703.6687.71983.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (9, '2005-01-14 17:35:39.548665', 'Firefox crashes when Save As dialog for a nonexistent window is closed', 12, NULL, NULL, '<20050114173539.6687.81610.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (10, '2004-10-05 00:00:00', 'Re: Bug Title Test', 12, NULL, NULL, '<20050831114528.7616.78129.malone@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (11, '2005-10-14 15:12:29.602117', 'A test bug', 16, NULL, NULL, '<20051014151229.28962.1536.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (12, '2005-10-14 12:25:21.508923', 'Re: Newly installed plug-in doesn''t seem to be used', 16, NULL, NULL, '<20051014122521.14276.39260.lptickets@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (13, '2005-10-14 13:28:11.554476', 'Re: Slow system', 12, NULL, NULL, '<20051014132811.14276.65873.lptickets@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (14, '2005-10-28 09:10:17.13237', 'Printing doesn''t work', 12, NULL, 3, '<20051028091017.6690.9505.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (15, '2006-02-23 16:42:14.080227', 'Thunderbird crashes', 16, NULL, 1, '<20060223164214.9126.7558.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (16, '2006-06-16 17:12:54', 'Unicodeâ„¢', 16, NULL, NULL, '<20060616141252.22134.71562@localhost.localdomain>', NULL, 51);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (17, '2006-02-22 19:42:21.890299', 'another test bug', 16, NULL, 1, '<20060222194221.25842.69665.malonedeb@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (18, '2006-07-20 20:48:24.975495', 'Re: Continue playing after shutdown', 16, NULL, NULL, '<20060720204825.13277.37433.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (19, '2006-07-20 20:49:47.551344', 'Re: mailto: problem in webpage', 16, NULL, NULL, '<20060720204947.13277.79684.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (20, '2006-07-20 20:52:07.054216', 'Re: Installation of Java Runtime Environment for Mozilla', 16, NULL, NULL, '<20060720205207.13277.68582.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (21, '2006-07-20 20:53:53.684848', 'Re: Play DVDs in Totem', 16, NULL, NULL, '<20060720205354.13277.37000.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (22, '2006-07-20 20:56:35.442839', 'Re: mailto: problem in webpage', 12, NULL, NULL, '<20060720205635.13277.87295.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (23, '2006-07-20 23:11:24.975495', 'Re: Continue playing after shutdown', 12, NULL, NULL, '<20061201222020.597.97888.lptickets@xxxxxxxxxxxxxxxxx>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (24, '2007-03-15 20:33:56.67893', 'Make Jokosher use autoaudiosink', 26, NULL, NULL, '<20070315203356.12919.76581.malonedeb@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (25, '2007-03-15 20:34:26.518114', 'Re: Make Jokosher use autoaudiosink', 50, NULL, NULL, '<20070315203426.12919.54628.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (26, '2007-03-15 20:35:10.133383', 'Re: Make Jokosher use autoaudiosink', 66, NULL, NULL, '<20070315203510.12919.22697.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (27, '2007-03-15 20:36:01.779544', 'Autoaudiosink is no longer under development', 63, NULL, NULL, '<20070315203601.12919.29640.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (28, '2007-03-15 20:36:57.133832', 'Re: Autoaudiosink is no longer under development', 27, NULL, NULL, '<20070315203657.12919.48585.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (29, '2007-03-15 20:37:27.991571', 'This is a really new title', 33, NULL, NULL, '<20070315203728.12919.76787.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (30, '2007-03-15 20:37:51.544376', 'Re: Make Jokosher use autoaudiosink', 3, NULL, NULL, '<20070315203751.12919.49072.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (31, '2007-03-15 20:41:18.635493', 'Copy, Cut and Delete operations should work on selections', 8, NULL, NULL, '<20070315204118.14326.61124.malonedeb@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (32, '2007-03-15 20:41:42.154264', 'Re: Copy, Cut and Delete operations should work on selections', 16, NULL, NULL, '<20070315204142.14326.82988.launchpad@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (33, '2007-03-15 20:43:14.981111', 'Re: Copy, Cut and Delete operations should work on selections', 45, NULL, NULL, '<20070315204315.14326.75272.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (34, '2007-03-15 20:45:15.852052', 'Re: Copy, Cut and Delete operations should work on selections', 13, NULL, NULL, '<20070315204515.14326.38817.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (35, '2007-03-15 20:45:51.817826', 'Re: Copy, Cut and Delete operations should work on selections', 9, NULL, NULL, '<20070315204551.14326.36994.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (36, '2007-03-15 20:46:49.83307', 'Re: Copy, Cut and Delete operations should work on selections', 6, NULL, NULL, '<20070315204649.14326.69581.malone@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (37, '2007-07-27 20:00:58.299796', 'Launchpad CSS and JS is not testible', 12, NULL, NULL, '<20070727200058.25131.76173.malonedeb@autumn.annrky-sinzui.local>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (38, '2007-07-27 20:29:46.25854', 'Re: Launchpad CSS and JS is not testible', 12, NULL, NULL, '<20070727202946.25131.16206.malone@autumn.annrky-sinzui.local>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (39, '2007-08-09 11:39:16.836856', 'jokosher exposes personal details in its actions portlet', 63, NULL, NULL, '<20070809113916.26819.83859.malonedeb@localhost.localdomain>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (40, '2004-12-18 16:30:19.103679', 'Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163019.18924.87555.malonedeb@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (41, '2007-12-18 16:30:47.889614', 'Re: Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163048.18924.13348.launchpad@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (42, '2007-12-18 16:30:47.889614', 'Re: Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163048.18924.86681.launchpad@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (43, '2007-12-18 16:31:34.790641', 'Re: Nonsensical bugs are useless', 62, NULL, NULL, '<20071218163134.18996.53651.launchpad@localhost>', NULL, NULL);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (44, '2005-05-13 16:37:37', 'gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, NULL, NULL, '<4284D7D1.6010208@xxxxxx>', NULL, 75);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (45, '2005-05-17 18:54:29', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243615, 44, NULL, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', NULL, 76);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (46, '2005-05-17 19:24:25', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, 45, NULL, '<428A44E9.6090802@xxxxxx>', NULL, 77);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (47, '2005-05-17 20:20:44', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243615, 46, NULL, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', NULL, 78);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (48, '2005-06-17 14:00:11', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243616, 46, NULL, '<20050617140011.GA15638@xxxxxxxxx>', NULL, 79);
-INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw) VALUES (49, '2005-06-25 10:13:10', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, NULL, NULL, '<42BD2E36.9090809@xxxxxx>', NULL, 81);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (1, '2004-09-24 20:58:04.684057', 'PEBCAK', 16, NULL, NULL, 'foo@xxxxxxxxxxx-332342--1231', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (3, '2004-09-24 21:17:17.153792', 'Reproduced on AIX', 12, NULL, NULL, 'sdsdfsfd', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (4, '2004-09-24 21:24:03.922564', 'Re: Reproduced on AIX', 12, NULL, NULL, 'sdfssfdfsd', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (5, '2004-09-24 21:29:27.407354', 'Fantastic idea, I''d really like to see this', 12, NULL, NULL, 'dxssdfsdgf', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (6, '2004-09-24 21:35:20.125564', 'Strange bug with duplicate messages.', 12, NULL, NULL, 'sdfsfwew', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (7, '2005-01-14 17:20:12.820778', 'Reflow problems with complex page layouts', 12, NULL, NULL, '<20050114172012.6687.51124.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (8, '2005-01-14 17:27:03.702622', 'Firefox install instructions should be complete', 12, NULL, NULL, '<20050114172703.6687.71983.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (9, '2005-01-14 17:35:39.548665', 'Firefox crashes when Save As dialog for a nonexistent window is closed', 12, NULL, NULL, '<20050114173539.6687.81610.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (10, '2004-10-05 00:00:00', 'Re: Bug Title Test', 12, NULL, NULL, '<20050831114528.7616.78129.malone@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (11, '2005-10-14 15:12:29.602117', 'A test bug', 16, NULL, NULL, '<20051014151229.28962.1536.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (12, '2005-10-14 12:25:21.508923', 'Re: Newly installed plug-in doesn''t seem to be used', 16, NULL, NULL, '<20051014122521.14276.39260.lptickets@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (13, '2005-10-14 13:28:11.554476', 'Re: Slow system', 12, NULL, NULL, '<20051014132811.14276.65873.lptickets@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (14, '2005-10-28 09:10:17.13237', 'Printing doesn''t work', 12, NULL, 3, '<20051028091017.6690.9505.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (15, '2006-02-23 16:42:14.080227', 'Thunderbird crashes', 16, NULL, 1, '<20060223164214.9126.7558.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (16, '2006-06-16 17:12:54', 'Unicodeâ„¢', 16, NULL, NULL, '<20060616141252.22134.71562@localhost.localdomain>', NULL, 51, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (17, '2006-02-22 19:42:21.890299', 'another test bug', 16, NULL, 1, '<20060222194221.25842.69665.malonedeb@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (18, '2006-07-20 20:48:24.975495', 'Re: Continue playing after shutdown', 16, NULL, NULL, '<20060720204825.13277.37433.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (19, '2006-07-20 20:49:47.551344', 'Re: mailto: problem in webpage', 16, NULL, NULL, '<20060720204947.13277.79684.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (20, '2006-07-20 20:52:07.054216', 'Re: Installation of Java Runtime Environment for Mozilla', 16, NULL, NULL, '<20060720205207.13277.68582.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (21, '2006-07-20 20:53:53.684848', 'Re: Play DVDs in Totem', 16, NULL, NULL, '<20060720205354.13277.37000.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (22, '2006-07-20 20:56:35.442839', 'Re: mailto: problem in webpage', 12, NULL, NULL, '<20060720205635.13277.87295.lptickets@xxxxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (23, '2006-07-20 23:11:24.975495', 'Re: Continue playing after shutdown', 12, NULL, NULL, '<20061201222020.597.97888.lptickets@xxxxxxxxxxxxxxxxx>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (24, '2007-03-15 20:33:56.67893', 'Make Jokosher use autoaudiosink', 26, NULL, NULL, '<20070315203356.12919.76581.malonedeb@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (25, '2007-03-15 20:34:26.518114', 'Re: Make Jokosher use autoaudiosink', 50, NULL, NULL, '<20070315203426.12919.54628.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (26, '2007-03-15 20:35:10.133383', 'Re: Make Jokosher use autoaudiosink', 66, NULL, NULL, '<20070315203510.12919.22697.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (27, '2007-03-15 20:36:01.779544', 'Autoaudiosink is no longer under development', 63, NULL, NULL, '<20070315203601.12919.29640.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (28, '2007-03-15 20:36:57.133832', 'Re: Autoaudiosink is no longer under development', 27, NULL, NULL, '<20070315203657.12919.48585.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (29, '2007-03-15 20:37:27.991571', 'This is a really new title', 33, NULL, NULL, '<20070315203728.12919.76787.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (30, '2007-03-15 20:37:51.544376', 'Re: Make Jokosher use autoaudiosink', 3, NULL, NULL, '<20070315203751.12919.49072.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (31, '2007-03-15 20:41:18.635493', 'Copy, Cut and Delete operations should work on selections', 8, NULL, NULL, '<20070315204118.14326.61124.malonedeb@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (32, '2007-03-15 20:41:42.154264', 'Re: Copy, Cut and Delete operations should work on selections', 16, NULL, NULL, '<20070315204142.14326.82988.launchpad@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (33, '2007-03-15 20:43:14.981111', 'Re: Copy, Cut and Delete operations should work on selections', 45, NULL, NULL, '<20070315204315.14326.75272.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (34, '2007-03-15 20:45:15.852052', 'Re: Copy, Cut and Delete operations should work on selections', 13, NULL, NULL, '<20070315204515.14326.38817.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (35, '2007-03-15 20:45:51.817826', 'Re: Copy, Cut and Delete operations should work on selections', 9, NULL, NULL, '<20070315204551.14326.36994.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (36, '2007-03-15 20:46:49.83307', 'Re: Copy, Cut and Delete operations should work on selections', 6, NULL, NULL, '<20070315204649.14326.69581.malone@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (37, '2007-07-27 20:00:58.299796', 'Launchpad CSS and JS is not testible', 12, NULL, NULL, '<20070727200058.25131.76173.malonedeb@autumn.annrky-sinzui.local>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (38, '2007-07-27 20:29:46.25854', 'Re: Launchpad CSS and JS is not testible', 12, NULL, NULL, '<20070727202946.25131.16206.malone@autumn.annrky-sinzui.local>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (39, '2007-08-09 11:39:16.836856', 'jokosher exposes personal details in its actions portlet', 63, NULL, NULL, '<20070809113916.26819.83859.malonedeb@localhost.localdomain>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (40, '2004-12-18 16:30:19.103679', 'Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163019.18924.87555.malonedeb@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (41, '2007-12-18 16:30:47.889614', 'Re: Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163048.18924.13348.launchpad@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (42, '2007-12-18 16:30:47.889614', 'Re: Nonsensical bugs are useless', 16, NULL, NULL, '<20071218163048.18924.86681.launchpad@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (43, '2007-12-18 16:31:34.790641', 'Re: Nonsensical bugs are useless', 62, NULL, NULL, '<20071218163134.18996.53651.launchpad@localhost>', NULL, NULL, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (44, '2005-05-13 16:37:37', 'gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, NULL, NULL, '<4284D7D1.6010208@xxxxxx>', NULL, 75, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (45, '2005-05-17 18:54:29', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243615, 44, NULL, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', NULL, 76, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (46, '2005-05-17 19:24:25', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, 45, NULL, '<428A44E9.6090802@xxxxxx>', NULL, 77, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (47, '2005-05-17 20:20:44', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243615, 46, NULL, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', NULL, 78, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (48, '2005-06-17 14:00:11', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243616, 46, NULL, '<20050617140011.GA15638@xxxxxxxxx>', NULL, 79, true);
+INSERT INTO message (id, datecreated, subject, owner, parent, distribution, rfc822msgid, fti, raw, visible) VALUES (49, '2005-06-25 10:13:10', 'Re: Bug#308994: gnome-volume-manager: dvd+rw unreadable when automounted in burner because mounted read/write', 243614, NULL, NULL, '<42BD2E36.9090809@xxxxxx>', NULL, 81, true);
 
 
 ALTER TABLE message ENABLE TRIGGER ALL;
@@ -3336,42 +3342,42 @@
 
 ALTER TABLE bugmessage DISABLE TRIGGER ALL;
 
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (1, 2, 1, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (2, 1, 3, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (3, 1, 4, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (4, 2, 5, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (5, 2, 6, NULL, NULL, true, 2);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (6, 4, 7, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (7, 5, 8, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (8, 6, 9, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (9, 3, 10, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (10, 7, 11, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (11, 8, 14, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (12, 9, 15, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (13, 10, 17, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (14, 10, 16, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (15, 11, 24, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (16, 11, 25, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (17, 11, 26, NULL, NULL, true, 2);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (18, 11, 27, NULL, NULL, true, 3);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (19, 11, 28, NULL, NULL, true, 4);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (20, 11, 29, NULL, NULL, true, 5);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (21, 11, 30, NULL, NULL, true, 6);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (22, 12, 31, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (23, 12, 33, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (24, 12, 34, NULL, NULL, true, 2);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (25, 12, 35, NULL, NULL, true, 3);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (26, 12, 36, NULL, NULL, true, 4);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (27, 13, 37, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (28, 13, 38, NULL, NULL, true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (29, 14, 39, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (30, 15, 40, NULL, NULL, true, 0);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@xxxxxx>', true, 1);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', true, 2);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (33, 15, 46, 11, '<428A44E9.6090802@xxxxxx>', true, 3);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', true, 4);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@xxxxxxxxx>', true, 5);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@xxxxxx>', true, 6);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (1, 2, 1, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (2, 1, 3, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (3, 1, 4, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (4, 2, 5, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (5, 2, 6, NULL, NULL, 2);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (6, 4, 7, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (7, 5, 8, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (8, 6, 9, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (9, 3, 10, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (10, 7, 11, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (11, 8, 14, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (12, 9, 15, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (13, 10, 17, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (14, 10, 16, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (15, 11, 24, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (16, 11, 25, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (17, 11, 26, NULL, NULL, 2);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (18, 11, 27, NULL, NULL, 3);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (19, 11, 28, NULL, NULL, 4);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (20, 11, 29, NULL, NULL, 5);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (21, 11, 30, NULL, NULL, 6);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (22, 12, 31, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (23, 12, 33, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (24, 12, 34, NULL, NULL, 2);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (25, 12, 35, NULL, NULL, 3);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (26, 12, 36, NULL, NULL, 4);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (27, 13, 37, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (28, 13, 38, NULL, NULL, 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (29, 14, 39, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (30, 15, 40, NULL, NULL, 0);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@xxxxxx>', 1);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', 2);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (33, 15, 46, 11, '<428A44E9.6090802@xxxxxx>', 3);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', 4);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@xxxxxxxxx>', 5);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, index) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@xxxxxx>', 6);
 
 
 ALTER TABLE bugmessage ENABLE TRIGGER ALL;
@@ -4000,6 +4006,13 @@
 ALTER TABLE databasecpustats ENABLE TRIGGER ALL;
 
 
+ALTER TABLE databasediskutilization DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE databasediskutilization ENABLE TRIGGER ALL;
+
+
 ALTER TABLE databasereplicationlag DISABLE TRIGGER ALL;
 
 
@@ -4389,6 +4402,20 @@
 ALTER TABLE featureflag ENABLE TRIGGER ALL;
 
 
+ALTER TABLE featureflagchangelogentry DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE featureflagchangelogentry ENABLE TRIGGER ALL;
+
+
+ALTER TABLE featureflagchangelogentry DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE featureflagchangelogentry ENABLE TRIGGER ALL;
+
+
 ALTER TABLE flatpackagesetinclusion DISABLE TRIGGER ALL;
 
 
@@ -9903,10 +9930,18 @@
 ALTER TABLE projectrelationship ENABLE TRIGGER ALL;
 
 
+ALTER TABLE publisherconfig DISABLE TRIGGER ALL;
+
+INSERT INTO publisherconfig (id, distribution, root_dir, base_url, copy_base_url) VALUES (1, 1, '/var/tmp/archive', 'http://archive.launchpad.dev/', 'http://rebuild-test.internal/');
+INSERT INTO publisherconfig (id, distribution, root_dir, base_url, copy_base_url) VALUES (2, 8, '/var/tmp/archive', 'http://archive.launchpad.dev/', 'http://rebuild-test.internal/');
+
+
+ALTER TABLE publisherconfig ENABLE TRIGGER ALL;
+
+
 ALTER TABLE pushmirroraccess DISABLE TRIGGER ALL;
 
 
-
 ALTER TABLE pushmirroraccess ENABLE TRIGGER ALL;
 
 

=== modified file 'database/schema/comments.sql'
--- database/schema/comments.sql	2011-02-28 15:51:56 +0000
+++ database/schema/comments.sql	2011-03-17 21:14:13 +0000
@@ -1098,7 +1098,7 @@
 COMMENT ON COLUMN Distribution.reviewer_whiteboard IS 'A whiteboard for Launchpad admins, registry experts and the project owners to capture the state of current issues with the project.';
 COMMENT ON COLUMN Distribution.max_bug_heat IS 'The highest heat value across bugs for this distribution.';
 COMMENT ON COLUMN Distribution.bug_reported_acknowledgement IS 'A message of acknowledgement to display to a bug reporter after they\'ve reported a new bug.';
-
+COMMENT ON COLUMN Distribution.registrant IS 'The person in launchpad who registered this distribution.';
 
 -- DistroSeries
 
@@ -1352,7 +1352,6 @@
 COMMENT ON TABLE BugMessage IS 'This table maps a message to a bug. In other words, it shows that a particular message is associated with a particular bug.';
 COMMENT ON COLUMN BugMessage.bugwatch IS 'The external bug this bug comment was imported from.';
 COMMENT ON COLUMN BugMessage.remote_comment_id IS 'The id this bug comment has in the external bug tracker, if it is an imported comment. If it is NULL while having a bugwatch set, this comment was added in Launchpad and needs to be pushed to the external bug tracker.';
-COMMENT ON COLUMN BugMessage.visible IS 'If false, the bug comment is hidden and should not be shown in any UI.';
 COMMENT ON COLUMN BugMessage.index IS 'The index (used in urls) of the message in a particular bug.';
 
 -- Messaging subsytem
@@ -1361,6 +1360,7 @@
 COMMENT ON COLUMN Message.subject IS 'The title text of the message, or the subject if it was an email.';
 COMMENT ON COLUMN Message.distribution IS 'The distribution in which this message originated, if we know it.';
 COMMENT ON COLUMN Message.raw IS 'The original unadulterated message if it arrived via email. This is required to provide access to the original, undecoded message.';
+COMMENT ON COLUMN Message.visible IS 'If false, the message is hidden and should not be shown in any UI.';
 
 COMMENT ON TABLE MessageChunk IS 'This table stores a single chunk of a possibly multipart message. There will be at least one row in this table for each message. text/* parts are stored in the content column. All other parts are stored in the Librarian and referenced via the blob column. If both content and blob are NULL, then this chunk has been removed (eg. offensive, legal reasons, virus etc.)';
 COMMENT ON COLUMN MessageChunk.content IS 'Text content for this chunk of the message. This content is full text searchable.';

=== modified file 'database/schema/launchpad_session.sql'
--- database/schema/launchpad_session.sql	2010-09-10 09:45:45 +0000
+++ database/schema/launchpad_session.sql	2011-03-17 21:14:13 +0000
@@ -29,3 +29,28 @@
 GRANT SELECT, INSERT, UPDATE, DELETE ON TimeLimitedToken TO session;
 -- And the garbo needs to run on it too.
 GRANT SELECT, DELETE ON TimeLimitedToken TO session;
+
+
+-- This helper needs to exist in the session database so the BulkPruner
+-- can clean up unwanted sessions.
+CREATE OR REPLACE FUNCTION cursor_fetch(cur refcursor, n integer)
+RETURNS SETOF record LANGUAGE plpgsql AS
+$$
+DECLARE
+    r record;
+    count integer;
+BEGIN
+    FOR count IN 1..n LOOP
+        FETCH FORWARD FROM cur INTO r;
+        IF NOT FOUND THEN
+            RETURN;
+        END IF;
+        RETURN NEXT r;
+    END LOOP;
+END;
+$$;
+
+COMMENT ON FUNCTION cursor_fetch(refcursor, integer) IS
+'Fetch the next n items from a cursor. Work around for not being able to use FETCH inside a SELECT statement.';
+
+GRANT EXECUTE ON FUNCTION cursor_fetch(refcursor, integer) TO session;

=== added file 'database/schema/patch-2208-51-0.sql'
--- database/schema/patch-2208-51-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-51-0.sql	2011-03-17 21:14:13 +0000
@@ -0,0 +1,18 @@
+SET client_min_messages=ERROR;
+
+--Create new visible column on message.
+ALTER TABLE Message
+    ADD COLUMN visible BOOLEAN NOT NULL DEFAULT TRUE;
+
+--Migrate the data, rebuilding indexes afterwards.
+UPDATE Message SET visible = FALSE
+    FROM BugMessage
+    WHERE BugMessage.message = Message.id AND BugMessage.visible IS FALSE;
+CLUSTER Message USING message_pkey;
+
+--And kill the old column
+ALTER TABLE BugMessage
+    DROP COLUMN visible;
+
+--Per patch adding reqs
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 51, 0);

=== added file 'database/schema/patch-2208-52-0.sql'
--- database/schema/patch-2208-52-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-52-0.sql	2011-03-17 21:14:13 +0000
@@ -0,0 +1,14 @@
+SET client_min_messages=ERROR;
+
+CREATE TABLE PublisherConfig (
+    id serial PRIMARY KEY,
+    distribution integer NOT NULL CONSTRAINT publisherconfig__distribution__fk REFERENCES distribution,
+    root_dir text NOT NULL,
+    base_url text NOT NULL,
+    copy_base_url text NOT NULL
+);
+    
+CREATE UNIQUE INDEX publisherconfig__distribution__idx
+    ON PublisherConfig(distribution);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 52, 0);

=== added file 'database/schema/patch-2208-53-0.sql'
--- database/schema/patch-2208-53-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-53-0.sql	2011-03-17 21:14:13 +0000
@@ -0,0 +1,20 @@
+-- Copyright 2009 Canonical Ltd.  This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+-- Add a registrant column to distributions.
+ALTER TABLE Distribution
+    ADD COLUMN registrant integer REFERENCES Person;
+
+-- Set registrant to owner for existing distros.
+update Distribution
+    SET registrant = owner;
+
+-- Add NOT NULL constraint to registrant column.
+ALTER TABLE Distribution ALTER COLUMN registrant SET NOT NULL;
+
+-- Add index to registrant column.
+CREATE INDEX distribution__registrant__idx ON Distribution(registrant);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 53, 0);

=== added file 'database/schema/patch-2208-54-0.sql'
--- database/schema/patch-2208-54-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-54-0.sql	2011-03-17 21:14:13 +0000
@@ -0,0 +1,6 @@
+SET client_min_messages=ERROR;
+
+ALTER TABLE distroarchseries
+    ADD CONSTRAINT valid_architecturetag CHECK (valid_name(architecturetag));
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 54, 0);

=== added file 'database/schema/patch-2208-99-0.sql'
--- database/schema/patch-2208-99-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-99-0.sql	2011-03-17 21:14:13 +0000
@@ -0,0 +1,23 @@
+-- Copyright 2011 Canonical Ltd.  This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+-- Convert DistroSeriesDifference source_version, parent_source_version,
+-- and base_version types to debversion.
+
+-- Change types.
+ALTER TABLE DistroSeriesDifference ALTER COLUMN source_version TYPE debversion;
+ALTER TABLE DistroSeriesDifference ALTER COLUMN parent_source_version TYPE debversion;
+ALTER TABLE DistroSeriesDifference ALTER COLUMN base_version TYPE debversion;
+
+-- Create indexes.
+CREATE INDEX SourcePackageRelease__source_version__idx
+    ON DistroSeriesDifference(source_version);
+CREATE INDEX BinaryPackageRelease__parent_source_version__idx
+    ON DistroSeriesDifference(parent_source_version);
+CREATE INDEX BinaryPackageRelease__base_version__idx
+    ON DistroSeriesDifference(base_version);
+
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 99, 0);

=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg	2011-03-14 21:07:09 +0000
+++ database/schema/security.cfg	2011-03-17 21:14:13 +0000
@@ -260,6 +260,7 @@
 public.productrelease                   = SELECT, INSERT, UPDATE, DELETE
 public.productreleasefile               = SELECT, INSERT, DELETE
 public.productseriescodeimport          = SELECT, INSERT, UPDATE
+public.publisherconfig                  = SELECT, INSERT, UPDATE, DELETE
 public.project                          = SELECT
 public.projectbounty                    = SELECT, INSERT, UPDATE
 public.questionbug                      = SELECT, INSERT, DELETE
@@ -859,6 +860,8 @@
 public.packagesetgroup                          = SELECT
 public.packagesetsources                        = SELECT, INSERT, UPDATE, DELETE
 public.packagesetinclusion                      = SELECT, INSERT, UPDATE, DELETE
+# INSERT for publisherconfig only required for the test suite.
+public.publisherconfig                          = SELECT, INSERT
 public.flatpackagesetinclusion                  = SELECT, INSERT, UPDATE, DELETE
 public.binarypackagepublishinghistory           = SELECT, INSERT, UPDATE, DELETE
 public.sourcepackagepublishinghistory           = SELECT, INSERT, UPDATE, DELETE
@@ -961,6 +964,7 @@
 public.teamparticipation                        = SELECT
 public.translationimportqueueentry              = SELECT, INSERT, UPDATE
 public.translationtemplatesbuild                = SELECT, INSERT
+public.publisherconfig                          = SELECT
 
 [ppa-apache-log-parser]
 type=user
@@ -1415,6 +1419,7 @@
 public.componentselection               = SELECT
 public.sectionselection                 = SELECT
 public.packagediff                      = SELECT, UPDATE
+public.publisherconfig                  = SELECT
 
 # Librarian stuff
 public.libraryfilealias                 = SELECT, INSERT
@@ -2258,6 +2263,7 @@
 public.distroseries                     = SELECT
 public.emailaddress                     = SELECT
 public.person                           = SELECT
+public.publisherconfig                  = SELECT
 public.teammembership                   = SELECT
 public.teamparticipation                = SELECT
 

=== modified file 'lib/canonical/config/schema-lazr.conf'
--- lib/canonical/config/schema-lazr.conf	2011-03-07 20:49:03 +0000
+++ lib/canonical/config/schema-lazr.conf	2011-03-17 21:14:13 +0000
@@ -24,15 +24,6 @@
 # datatype: string
 dbuser: archivepublisher
 
-# Base directory for distribution archives.
-# datatype: string
-root: /var/tmp/archive/
-
-# External base URL for distribution archives.
-# datatype: string
-base_url: http://ftpmaster.internal/
-copy_base_url: http://rebuild-test.internal/
-
 
 [binaryfile_expire]
 dbuser: binaryfile-expire

=== modified file 'lib/canonical/launchpad/database/message.py'
--- lib/canonical/launchpad/database/message.py	2011-03-04 03:48:11 +0000
+++ lib/canonical/launchpad/database/message.py	2011-03-17 21:14:13 +0000
@@ -37,6 +37,7 @@
     )
 import pytz
 from sqlobject import (
+    BoolCol,
     ForeignKey,
     IntCol,
     SQLMultipleJoin,
@@ -131,6 +132,7 @@
     raw = ForeignKey(foreignKey='LibraryFileAlias', dbName='raw',
                      default=None)
     bugattachments = SQLMultipleJoin('BugAttachment', joinColumn='_message')
+    visible = BoolCol(notNull=True, default=True)
 
     def __repr__(self):
         return "<Message at 0x%x id=%s>" % (id(self), self.id)

=== modified file 'lib/canonical/launchpad/interfaces/message.py'
--- lib/canonical/launchpad/interfaces/message.py	2010-10-03 15:30:06 +0000
+++ lib/canonical/launchpad/interfaces/message.py	2011-03-17 21:14:13 +0000
@@ -104,6 +104,8 @@
         title=_("Whether or not the title of this message "
                 "is different to that of its parent."),
         readonly=True)
+    visible = Bool(title=u"This message is visible or not.", required=False,
+        default=True)
 
     bugattachments = exported(
         CollectionField(

=== modified file 'lib/canonical/launchpad/pagetests/webservice/xx-hide-comments.txt'
--- lib/canonical/launchpad/pagetests/webservice/xx-hide-comments.txt	2010-10-11 16:17:45 +0000
+++ lib/canonical/launchpad/pagetests/webservice/xx-hide-comments.txt	2011-03-17 21:14:13 +0000
@@ -27,7 +27,7 @@
     >>> bug_11 = getUtility(IBugSet).get(11)
     >>> bug_message = getUtility(IBugMessageSet).getByBugAndMessage(
     ...     bug_11, bug_11.messages[2])
-    >>> bug_message.visible
+    >>> bug_message.message.visible
     False
     >>> logout()
 
@@ -46,7 +46,7 @@
     >>> bug_11 = getUtility(IBugSet).get(11)
     >>> bug_message = getUtility(IBugMessageSet).getByBugAndMessage(
     ...     bug_11, bug_11.messages[2])
-    >>> bug_message.visible
+    >>> bug_message.message.visible
     True
     >>> logout()
 

=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py	2011-02-21 15:22:33 +0000
+++ lib/canonical/launchpad/security.py	2011-03-17 21:14:13 +0000
@@ -35,6 +35,7 @@
     IOAuthAccessToken,
     IOAuthRequestToken,
     )
+from canonical.launchpad.interfaces.message import IMessage
 from canonical.launchpad.webapp.authorization import check_permission
 from canonical.launchpad.webapp.interfaces import (
     IAuthorization,
@@ -45,6 +46,7 @@
 from lp.answers.interfaces.question import IQuestion
 from lp.answers.interfaces.questionsperson import IQuestionsPerson
 from lp.answers.interfaces.questiontarget import IQuestionTarget
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfig
 from lp.blueprints.interfaces.specification import (
     ISpecification,
     ISpecificationPublic,
@@ -2587,3 +2589,16 @@
         if parent is None:
             return False
         return check_permission(self.permission, parent)
+    
+
+class SetMessageVisibility(AuthorizationBase):
+    permission = 'launchpad.Admin'
+    usedfor = IMessage
+
+    def checkAuthenticated(self, user):
+        """Admins and registry admins can set bug comment visibility."""
+        return (user.in_admin or user.in_registry_experts)
+
+
+class ViewPublisherConfig(AdminByAdminsTeam):
+    usedfor = IPublisherConfig

=== modified file 'lib/canonical/launchpad/zcml/message.zcml'
--- lib/canonical/launchpad/zcml/message.zcml	2010-10-03 15:30:06 +0000
+++ lib/canonical/launchpad/zcml/message.zcml	2011-03-17 21:14:13 +0000
@@ -9,11 +9,12 @@
     i18n_domain="launchpad">
 
     <!-- Message -->
-    <class class="canonical.launchpad.database.message.Message">
+    <class
+        class="canonical.launchpad.database.message.Message">
         <allow interface="canonical.launchpad.interfaces.message.IMessage" />
         <require
-            permission="zope.Public"
-            set_schema="canonical.launchpad.interfaces.message.IMessage" />
+            permission="launchpad.Admin"
+            set_attributes="visible"/>
     </class>
 
     <class class="canonical.launchpad.interfaces.message.IndexedMessage">

=== modified file 'lib/lp/app/stories/launchpad-root/site-search.txt'
--- lib/lp/app/stories/launchpad-root/site-search.txt	2011-02-17 17:02:54 +0000
+++ lib/lp/app/stories/launchpad-root/site-search.txt	2011-03-17 21:14:13 +0000
@@ -181,9 +181,11 @@
     >>> print_search_results()
     Exact matches
     Ubuntu
-    Ubuntu is a new approach to Linux Distribution that includes regular
-    releases, and a simplified single-CD installation system.
-    Registered on 2006-10-16
+    Ubuntu is a new approach to Linux Distribution that includes ...
+    Registered
+    by
+    Registry Administrators
+    on 2006-10-16
 
 The user enters the number 1, and he sees a bug and a question in the
 "Exact matches" section.

=== modified file 'lib/lp/archivepublisher/config.py'
--- lib/lp/archivepublisher/config.py	2010-10-17 13:35:20 +0000
+++ lib/lp/archivepublisher/config.py	2011-03-17 21:14:13 +0000
@@ -8,7 +8,10 @@
 
 import os
 
+from zope.component import getUtility
+
 from canonical.config import config
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.soyuz.enums import ArchivePurpose
 
 
@@ -24,9 +27,11 @@
     """
     pubconf = Config()
     ppa_config = config.personalpackagearchive
+    db_pubconf = getUtility(
+        IPublisherConfigSet).getByDistribution(archive.distribution)
 
     pubconf.temproot = os.path.join(
-        config.archivepublisher.root, '%s-temp' % archive.distribution.name)
+        db_pubconf.root_dir, '%s-temp' % archive.distribution.name)
 
     if archive.is_ppa:
         if archive.private:
@@ -40,7 +45,7 @@
             pubconf.distroroot, archive.owner.name, archive.name,
             archive.distribution.name)
     elif archive.is_main:
-        pubconf.distroroot = config.archivepublisher.root
+        pubconf.distroroot = db_pubconf.root_dir
         pubconf.archiveroot = os.path.join(
             pubconf.distroroot, archive.distribution.name)
         if archive.purpose == ArchivePurpose.PARTNER:
@@ -48,7 +53,7 @@
         elif archive.purpose == ArchivePurpose.DEBUG:
             pubconf.archiveroot += '-debug'
     elif archive.is_copy:
-        pubconf.distroroot = config.archivepublisher.root
+        pubconf.distroroot = db_pubconf.root_dir
         pubconf.archiveroot = os.path.join(
             pubconf.distroroot,
             archive.distribution.name + '-' + archive.name,
@@ -83,10 +88,6 @@
     return pubconf
 
 
-class LucilleConfigError(Exception):
-    """Lucille configuration was not present."""
-
-
 class Config(object):
     """Manage a publisher configuration from the database. (Read Only)
     This class provides a useful abstraction so that if we change

=== modified file 'lib/lp/archivepublisher/deathrow.py'
--- lib/lp/archivepublisher/deathrow.py	2010-10-17 13:35:20 +0000
+++ lib/lp/archivepublisher/deathrow.py	2011-03-17 21:14:13 +0000
@@ -17,7 +17,6 @@
 from lp.archivepublisher import ELIGIBLE_DOMINATION_STATES
 from lp.archivepublisher.config import (
     getPubConfig,
-    LucilleConfigError,
     )
 from lp.archivepublisher.diskpool import DiskPool
 from lp.archivepublisher.utils import process_in_batches
@@ -40,12 +39,8 @@
          the one provided by the publishing-configuration, it will be only
          used for PRIMARY archives.
     """
-    log.debug("Grab Lucille config.")
-    try:
-        pubconf = getPubConfig(archive)
-    except LucilleConfigError, info:
-        log.error(info)
-        raise
+    log.debug("Grab publisher config.")
+    pubconf = getPubConfig(archive)
 
     if (pool_root_override is not None and
         archive.purpose == ArchivePurpose.PRIMARY):

=== added file 'lib/lp/archivepublisher/interfaces/publisherconfig.py'
--- lib/lp/archivepublisher/interfaces/publisherconfig.py	1970-01-01 00:00:00 +0000
+++ lib/lp/archivepublisher/interfaces/publisherconfig.py	2011-03-17 21:14:13 +0000
@@ -0,0 +1,58 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+# pylint: disable-msg=E0211,E0213
+
+"""PublisherConfig interface."""
+
+__metaclass__ = type
+
+__all__ = [
+    'IPublisherConfig',
+    'IPublisherConfigSet',
+    ]
+
+from lazr.restful.fields import Reference
+from zope.interface import Interface
+from zope.schema import (
+    Int,
+    TextLine,
+    )
+
+from canonical.launchpad import _
+from lp.registry.interfaces.distribution import IDistribution
+
+
+class IPublisherConfig(Interface):
+    """`PublisherConfig` interface."""
+
+    id = Int(title=_('ID'), required=True, readonly=True)
+
+    distribution = Reference(
+        IDistribution, title=_("Distribution"), required=True,
+        description=_("The Distribution for this configuration."))
+
+    root_dir = TextLine(
+        title=_("Root Directory"), required=True,
+        description=_("The root directory for published archives."))
+
+    base_url = TextLine(
+        title=_("Base URL"), required=True,
+        description=_("The base URL for published archives"))
+
+    copy_base_url = TextLine(
+        title=_("Copy Base URL"), required=True,
+        description=_("The base URL for published copy archives"))
+
+
+class IPublisherConfigSet(Interface):
+    """`PublisherConfigSet` interface."""
+
+    def new(distribution, root_dir, base_url, copy_base_url):
+        """Create a new `PublisherConfig`."""
+
+    def getByDistribution(distribution):
+        """Get the config for a a distribution.
+
+        :param distribution: An `IDistribution`
+        """

=== added file 'lib/lp/archivepublisher/model/publisherconfig.py'
--- lib/lp/archivepublisher/model/publisherconfig.py	1970-01-01 00:00:00 +0000
+++ lib/lp/archivepublisher/model/publisherconfig.py	2011-03-17 21:14:13 +0000
@@ -0,0 +1,68 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Database class for table PublisherConfig."""
+
+__metaclass__ = type
+
+__all__ = [
+    'PublisherConfig',
+    'PublisherConfigSet',
+    ]
+
+from storm.locals import (
+    Int,
+    Reference,
+    Storm,
+    Unicode,
+    )
+from zope.interface import implements
+
+from canonical.launchpad.interfaces.lpstorm import (
+    IMasterStore,
+    )
+from lp.archivepublisher.interfaces.publisherconfig import (
+    IPublisherConfig,
+    IPublisherConfigSet,
+    )
+
+
+class PublisherConfig(Storm):
+    """See `IArchiveAuthToken`."""
+    implements(IPublisherConfig)
+    __storm_table__ = 'PublisherConfig'
+
+    id = Int(primary=True)
+
+    distribution_id = Int(name='distribution', allow_none=False)
+    distribution = Reference(distribution_id, 'Distribution.id')
+
+    root_dir = Unicode(name='root_dir', allow_none=False)
+
+    base_url = Unicode(name='base_url', allow_none=False)
+
+    copy_base_url = Unicode(name='copy_base_url', allow_none=False)
+
+
+class PublisherConfigSet:
+    """See `IPublisherConfigSet`."""
+    implements(IPublisherConfigSet)
+    title = "Soyuz Publisher Configurations"
+
+    def new(self, distribution, root_dir, base_url, copy_base_url):
+        """Make and return a new `PublisherConfig`."""
+        store = IMasterStore(PublisherConfig)
+        pubconf = PublisherConfig()
+        pubconf.distribution = distribution
+        pubconf.root_dir = root_dir
+        pubconf.base_url = base_url
+        pubconf.copy_base_url = copy_base_url
+        store.add(pubconf)
+        return pubconf
+
+    def getByDistribution(self, distribution):
+        """See `IArchiveAuthTokenSet`."""
+        store = IMasterStore(PublisherConfig)
+        return store.find(
+            PublisherConfig,
+            PublisherConfig.distribution_id == distribution.id).one()

=== modified file 'lib/lp/archivepublisher/publishing.py'
--- lib/lp/archivepublisher/publishing.py	2011-02-04 09:07:36 +0000
+++ lib/lp/archivepublisher/publishing.py	2011-03-17 21:14:13 +0000
@@ -21,7 +21,6 @@
 from lp.archivepublisher import HARDCODED_COMPONENT_ORDER
 from lp.archivepublisher.config import (
     getPubConfig,
-    LucilleConfigError,
     )
 from lp.archivepublisher.diskpool import DiskPool
 from lp.archivepublisher.domination import Dominator
@@ -120,11 +119,7 @@
     else:
         log.debug("Finding configuration for '%s' PPA."
                   % archive.owner.name)
-    try:
-        pubconf = getPubConfig(archive)
-    except LucilleConfigError, info:
-        log.error(info)
-        raise
+    pubconf = getPubConfig(archive)
 
     disk_pool = _getDiskPool(pubconf, log)
 

=== added file 'lib/lp/archivepublisher/tests/test_publisherconfig.py'
--- lib/lp/archivepublisher/tests/test_publisherconfig.py	1970-01-01 00:00:00 +0000
+++ lib/lp/archivepublisher/tests/test_publisherconfig.py	2011-03-17 21:14:13 +0000
@@ -0,0 +1,92 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for publisherConfig model class."""
+
+__metaclass__ = type
+
+
+from storm.store import Store
+from storm.exceptions import IntegrityError
+from zope.component import getUtility
+from zope.interface.verify import verifyObject
+from zope.security.interfaces import Unauthorized
+
+from canonical.launchpad.ftests import login
+from canonical.testing.layers import (
+    DatabaseFunctionalLayer,
+    ZopelessDatabaseLayer,
+    )
+from lp.archivepublisher.interfaces.publisherconfig import (
+    IPublisherConfig,
+    IPublisherConfigSet,
+    )
+from lp.testing import (
+    ANONYMOUS,
+    TestCaseWithFactory,
+    )
+from lp.testing.sampledata import LAUNCHPAD_ADMIN
+
+
+class TestPublisherConfig(TestCaseWithFactory):
+    """Test the `PublisherConfig` model."""
+    layer = ZopelessDatabaseLayer
+
+    def setUp(self):
+        TestCaseWithFactory.setUp(self)
+        self.distribution = self.factory.makeDistribution(name='conftest')
+
+    def test_verify_interface(self):
+        # Test the interface for the model.
+        pubconf = self.factory.makePublisherConfig()
+        verified = verifyObject(IPublisherConfig, pubconf)
+        self.assertTrue(verified)
+
+    def test_properties(self):
+        # Test the model properties.
+        ROOT_DIR = u"rootdir/test"
+        BASE_URL = u"http://base.url";
+        COPY_BASE_URL = u"http://base.url";
+        pubconf = self.factory.makePublisherConfig(
+            distribution=self.distribution,
+            root_dir=ROOT_DIR,
+            base_url=BASE_URL,
+            copy_base_url=COPY_BASE_URL,
+            )
+
+        self.assertEqual(self.distribution.name, pubconf.distribution.name)
+        self.assertEqual(ROOT_DIR, pubconf.root_dir)
+        self.assertEqual(BASE_URL, pubconf.base_url)
+        self.assertEqual(COPY_BASE_URL, pubconf.copy_base_url)
+
+    def test_one_config_per_distro(self):
+        # Only one config for each distro is allowed.
+        pubconf = self.factory.makePublisherConfig(self.distribution)
+        pubconf2 = self.factory.makePublisherConfig(self.distribution)
+        store = Store.of(pubconf)
+        self.assertRaises(IntegrityError, store.flush)
+
+    def test_getByDistribution(self):
+        # Test that IPublisherConfigSet.getByDistribution works.
+        pubconf = getUtility(IPublisherConfigSet).getByDistribution(
+            self.distribution)
+        self.assertEqual(self.distribution.name, pubconf.distribution.name)
+
+
+class TestPublisherConfigSecurity(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_only_admin(self):
+        # Only admins can see and change the config.
+        distro = self.factory.makeDistribution(publish_root_dir=u"foo")
+        config = getUtility(IPublisherConfigSet).getByDistribution(distro)
+
+        login(ANONYMOUS)
+        self.assertRaises(Unauthorized, getattr, config, "root_dir")
+        self.assertRaises(Unauthorized, setattr, config, "root_dir", "test")
+
+        login(LAUNCHPAD_ADMIN)
+        self.assertEqual(u"foo", config.root_dir)
+        config.root_dir = u"bar"
+        self.assertEqual(u"bar", config.root_dir)

=== modified file 'lib/lp/archivepublisher/zcml/configure.zcml'
--- lib/lp/archivepublisher/zcml/configure.zcml	2009-07-13 18:15:02 +0000
+++ lib/lp/archivepublisher/zcml/configure.zcml	2011-03-17 21:14:13 +0000
@@ -2,8 +2,32 @@
      GNU Affero General Public License version 3 (see the file LICENSE).
 -->
 
-<configure xmlns="http://namespaces.zope.org/zope";>
+<configure
+    xmlns="http://namespaces.zope.org/zope";
+    xmlns:browser="http://namespaces.zope.org/browser";
+    xmlns:i18n="http://namespaces.zope.org/i18n";
+    xmlns:webservice="http://namespaces.canonical.com/webservice";
+    xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc";
+    i18n_domain="launchpad">
+
     <include package="lp.archivepublisher.zcml"
              file="archivesigningkey.zcml" />
+
+    <securedutility
+        class="lp.archivepublisher.model.publisherconfig.PublisherConfigSet"
+        provides="lp.archivepublisher.interfaces.publisherconfig.IPublisherConfigSet">
+        <allow
+            interface="lp.archivepublisher.interfaces.publisherconfig.IPublisherConfigSet"/>
+    </securedutility>
+
+    <class
+        class="lp.archivepublisher.model.publisherconfig.PublisherConfig">
+        <require
+            permission="launchpad.Admin"
+            interface="lp.archivepublisher.interfaces.publisherconfig.IPublisherConfig"
+            set_schema="lp.archivepublisher.interfaces.publisherconfig.IPublisherConfig"/>
+    </class>
+
+
 </configure>
 

=== modified file 'lib/lp/bugs/browser/bugcomment.py'
--- lib/lp/bugs/browser/bugcomment.py	2011-02-16 20:06:40 +0000
+++ lib/lp/bugs/browser/bugcomment.py	2011-03-17 21:14:13 +0000
@@ -68,7 +68,7 @@
         bug_comment = comments.get(message.id)
         if bug_comment is None:
             bug_comment = BugComment(bugmessage.index, message, bugtask,
-                visible=bugmessage.visible)
+                visible=message.visible)
             comments[message.id] = bug_comment
             # This code path is currently only used from BugTask view which
             # have already loaded all the bug watches. If we start lazy loading

=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml	2011-03-16 13:26:30 +0000
+++ lib/lp/bugs/configure.zcml	2011-03-17 21:14:13 +0000
@@ -857,7 +857,7 @@
             interface="lp.bugs.interfaces.bugmessage.IBugMessage"/>
         <require
             permission="launchpad.Admin"
-            set_attributes="remote_comment_id bugwatch visible index"/>
+            set_attributes="remote_comment_id bugwatch index"/>
     </class>
 
     <!-- BugMessageSet -->
@@ -1048,6 +1048,10 @@
         class="lp.bugs.mail.bugnotificationrecipients.BugNotificationRecipients">
         <allow
             interface="canonical.launchpad.interfaces.launchpad.INotificationRecipientSet"/>
+        <!-- BugNotificationRecipients provides the following
+             attributes/methods in addition. -->
+        <allow
+            attributes="subscription_filters addFilter"/>
     </class>
     <securedutility
         provides="lp.bugs.interfaces.bugnotification.IBugNotificationSet"

=== modified file 'lib/lp/bugs/doc/bugmessage-visibility.txt'
--- lib/lp/bugs/doc/bugmessage-visibility.txt	2010-10-18 22:24:59 +0000
+++ lib/lp/bugs/doc/bugmessage-visibility.txt	2011-03-17 21:14:13 +0000
@@ -20,27 +20,28 @@
     ...     content="new message for visibility test",
     ...     owner=sample_person,
     ...     bug=bug_one)
-    >>> new_message.visible
+    >>> new_message.message.visible
     True
 
-Only Admins can hide bug messages using the visible field.
+Only Admins and registry experts can hide bug messages using the visible
+field.
 
     # Login an Admin user and set visible to False.
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> abugmessage = bugmessageset.get(1)
-    >>> abugmessage.visible
+    >>> abugmessage.message.visible
     True
-    >>> abugmessage.visible = False
-    >>> abugmessage.visible
+    >>> abugmessage.message.visible = False
+    >>> abugmessage.message.visible
     False
 
     # Non-privileged users should not be able to
     # make comments visible again.
     >>> login('no-priv@xxxxxxxxxxxxx')
     >>> abugmessage = bugmessageset.get(1)
-    >>> abugmessage.visible
+    >>> abugmessage.message.visible
     False
-    >>> abugmessage.visible = True
+    >>> abugmessage.message.visible = True
     Traceback (most recent call last):
     ...
-    Unauthorized: (<BugMessage at ...>, 'visible', 'launchpad.Admin')
+    Unauthorized: (<Message at ...>, 'visible', 'launchpad.Admin')

=== modified file 'lib/lp/bugs/doc/bugnotification-sending.txt'
--- lib/lp/bugs/doc/bugnotification-sending.txt	2011-02-22 10:44:48 +0000
+++ lib/lp/bugs/doc/bugnotification-sending.txt	2011-03-17 21:14:13 +0000
@@ -21,8 +21,10 @@
 
     >>> def print_notification_headers(email_notification):
     ...     for header in ['To', 'From', 'Subject',
-    ...                    'X-Launchpad-Message-Rationale']:
-    ...         print "%s: %s" % (header, email_notification[header])
+    ...                    'X-Launchpad-Message-Rationale',
+    ...                    'X-Launchpad-Subscription-Filter']:
+    ...         if email_notification[header]:
+    ...             print "%s: %s" % (header, email_notification[header])
 
     >>> def print_notification(email_notification):
     ...     print_notification_headers(email_notification)
@@ -676,7 +678,6 @@
 been set properly.
 
     >>> from lp.bugs.model.bugnotification import BugNotification
-    >>> from lp.bugs.enum import BugNotificationStatus
     >>> for notification in BugNotification.select(orderBy='id')[-8:]:
     ...     if notification.is_comment:
     ...         identifier = 'comment'
@@ -1247,6 +1248,7 @@
     >>> with lp_dbuser():
     ...     filter = subscription_no_priv.newBugFilter()
     ...     filter.bug_notification_level = BugNotificationLevel.COMMENTS
+    ...     filter.description = u"Allow-comments filter"
 
     >>> comment = getUtility(IMessageSet).fromText(
     ...     'subject', 'another comment.', sample_person,
@@ -1279,12 +1281,14 @@
     From: Sample Person <...@bugs.launchpad.net>
     Subject: [Bug 1] subject
     X-Launchpad-Message-Rationale: Subscriber (Mozilla Firefox)
+    X-Launchpad-Subscription-Filter: Allow-comments filter
     <BLANKLINE>
     another comment.
     <BLANKLINE>
     --
     You received this bug notification because you are subscribed to Mozilla
     Firefox.
+    Matching filters: Allow-comments filter
     ...
     ----------------------------------------------------------------------
     To: support@xxxxxxxxxx
@@ -1390,6 +1394,7 @@
     From: Sample Person <...@bugs.launchpad.net>
     Subject: [Bug 1] subject
     X-Launchpad-Message-Rationale: Subscriber (Mozilla Firefox)
+    X-Launchpad-Subscription-Filter: Allow-comments filter
     <BLANKLINE>
     no comment for no-priv.
     <BLANKLINE>
@@ -1400,6 +1405,7 @@
     --
     You received this bug notification because you are subscribed to Mozilla
     Firefox.
+    Matching filters: Allow-comments filter
     ...
     ----------------------------------------------------------------------
     To: support@xxxxxxxxxx

=== modified file 'lib/lp/bugs/enum.py'
--- lib/lp/bugs/enum.py	2011-03-05 00:06:26 +0000
+++ lib/lp/bugs/enum.py	2011-03-17 21:14:13 +0000
@@ -57,18 +57,18 @@
 
 class BugNotificationStatus(DBEnumeratedType):
     """The status of a bug notification.
-    
+
     A notification may be pending, sent, or omitted."""
 
     PENDING = DBItem(10, """
         Pending
-        
+
         The notification has not yet been sent.
         """)
 
     OMITTED = DBItem(20, """
         Omitted
-        
+
         The system considered sending the notification, but omitted it.
         This is generally because the action reported by the notification
         was immediately undone.
@@ -76,6 +76,6 @@
 
     SENT = DBItem(30, """
         Sent
-        
+
         The notification has been sent.
         """)

=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py	2011-03-17 14:26:42 +0000
+++ lib/lp/bugs/interfaces/bug.py	2011-03-17 21:14:13 +0000
@@ -382,7 +382,7 @@
         title=_('Heat Last Updated'), required=False, readonly=True)
 
     # Adding related BugMessages provides a hook for getting at
-    # BugMessage.visible when building bug comments.
+    # BugMessage.message.visible when building bug comments.
     bug_messages = Attribute('The bug messages related to this object.')
     comment_count = Attribute(
         "The number of comments on this bug, not including the initial "

=== modified file 'lib/lp/bugs/interfaces/bugmessage.py'
--- lib/lp/bugs/interfaces/bugmessage.py	2011-02-24 15:30:54 +0000
+++ lib/lp/bugs/interfaces/bugmessage.py	2011-03-17 21:14:13 +0000
@@ -52,8 +52,6 @@
     bugwatchID = Int(title=u'The bugwatch id.', readonly=True)
     remote_comment_id = TextLine(
         title=u"The id this comment has in the bugwatch's bug tracker.")
-    visible = Bool(title=u"This message is visible or not.", required=False,
-        default=True)
 
 
 class IBugMessageSet(Interface):

=== modified file 'lib/lp/bugs/mail/bugnotificationrecipients.py'
--- lib/lp/bugs/mail/bugnotificationrecipients.py	2011-03-02 14:07:23 +0000
+++ lib/lp/bugs/mail/bugnotificationrecipients.py	2011-03-17 21:14:13 +0000
@@ -58,6 +58,7 @@
         """
         NotificationRecipientSet.__init__(self)
         self.duplicateof = duplicateof
+        self.subscription_filters = set()
 
     def _addReason(self, person, reason, header):
         """Adds a reason (text and header) for a person.
@@ -127,3 +128,13 @@
         else:
             text = "are the registrant for %s" % upstream.displayname
         self._addReason(person, text, reason)
+
+    def update(self, recipient_set):
+        """See `INotificationRecipientSet`."""
+        super(BugNotificationRecipients, self).update(recipient_set)
+        self.subscription_filters.update(
+            recipient_set.subscription_filters)
+
+    def addFilter(self, subscription_filter):
+        if subscription_filter is not None:
+            self.subscription_filters.add(subscription_filter)

=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py	2011-03-16 13:26:30 +0000
+++ lib/lp/bugs/model/bug.py	2011-03-17 21:14:13 +0000
@@ -1791,7 +1791,7 @@
         bug_message_set = getUtility(IBugMessageSet)
         bug_message = bug_message_set.getByBugAndMessage(
             self, self.messages[comment_number])
-        bug_message.visible = visible
+        bug_message.message.visible = visible
 
     @cachedproperty
     def _known_viewers(self):

=== modified file 'lib/lp/bugs/model/bugmessage.py'
--- lib/lp/bugs/model/bugmessage.py	2011-02-16 03:35:46 +0000
+++ lib/lp/bugs/model/bugmessage.py	2011-03-17 21:14:13 +0000
@@ -44,7 +44,6 @@
     bugwatch = ForeignKey(dbName='bugwatch', foreignKey='BugWatch',
         notNull=False, default=None)
     remote_comment_id = StringCol(notNull=False, default=None)
-    visible = BoolCol(notNull=True, default=True)
     # -- The index of the message is cached in the DB.
     index = IntCol(notNull=True)
 

=== modified file 'lib/lp/bugs/model/bugnotification.py'
--- lib/lp/bugs/model/bugnotification.py	2011-02-25 16:19:58 +0000
+++ lib/lp/bugs/model/bugnotification.py	2011-03-17 21:14:13 +0000
@@ -150,12 +150,23 @@
             reason_body, reason_header = recipients.getReason(recipient)
             sql_values.append('(%s, %s, %s, %s)' % sqlvalues(
                 bug_notification, recipient, reason_header, reason_body))
+
         # We add all the recipients in a single SQL statement to make
         # this a bit more efficient for bugs with many subscribers.
         store.execute("""
             INSERT INTO BugNotificationRecipient
               (bug_notification, person, reason_header, reason_body)
             VALUES %s;""" % ', '.join(sql_values))
+
+        if len(recipients.subscription_filters) > 0:
+            filter_link_sql = [
+                "(%s, %s)" % sqlvalues(bug_notification, filter.id)
+                for filter in recipients.subscription_filters]
+            store.execute("""
+                INSERT INTO BugNotificationFilter
+                  (bug_notification, bug_subscription_filter)
+                VALUES %s;""" % ", ".join(filter_link_sql))
+
         return bug_notification
 
 

=== modified file 'lib/lp/bugs/model/structuralsubscription.py'
--- lib/lp/bugs/model/structuralsubscription.py	2011-03-11 22:15:40 +0000
+++ lib/lp/bugs/model/structuralsubscription.py	2011-03-17 21:14:13 +0000
@@ -611,14 +611,15 @@
     else:
         subscribers = []
         query_results = source.find(
-            (Person, StructuralSubscription),
-            *constraints).config(distinct=True)
-        for person, subscription in query_results:
+            (Person, StructuralSubscription, BugSubscriptionFilter),
+            *constraints)
+        for person, subscription, filter in query_results:
             # Set up results.
             if person not in recipients:
                 subscribers.append(person)
                 recipients.addStructuralSubscriber(
                     person, subscription.target)
+            recipients.addFilter(filter)
         return subscribers
 
 

=== modified file 'lib/lp/bugs/security.py'
--- lib/lp/bugs/security.py	2011-02-28 22:09:39 +0000
+++ lib/lp/bugs/security.py	2011-03-17 21:14:13 +0000
@@ -161,20 +161,15 @@
     usedfor = IMessage
 
 
-class SetMessageVisibility(AuthorizationBase):
+class SetBugCommentVisibility(AuthorizationBase):
     permission = 'launchpad.Admin'
-    usedfor = IBugMessage
+    usedfor = IBug
 
     def checkAuthenticated(self, user):
         """Admins and registry admins can set bug comment visibility."""
         return (user.in_admin or user.in_registry_experts)
 
 
-class SetBugCommentVisibility(SetMessageVisibility):
-
-    usedfor = IBug
-
-
 class ViewBugTracker(AnonymousAuthorization):
     """Anyone can view a bug tracker."""
     usedfor = IBugTracker

=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-hidden-comments.txt'
--- lib/lp/bugs/stories/bugs/xx-bug-hidden-comments.txt	2010-10-11 16:17:45 +0000
+++ lib/lp/bugs/stories/bugs/xx-bug-hidden-comments.txt	2011-03-17 21:14:13 +0000
@@ -30,7 +30,7 @@
     This comment will not be visible when the test completes.
     >>> bug_message = getUtility(IBugMessageSet).getByBugAndMessage(
     ...     bug_11, message)
-    >>> bug_message.visible = False
+    >>> bug_message.message.visible = False
     >>> transaction.commit()
     >>> logout()
 

=== modified file 'lib/lp/bugs/tests/test_bug_messages_webservice.py'
--- lib/lp/bugs/tests/test_bug_messages_webservice.py	2011-03-07 13:57:30 +0000
+++ lib/lp/bugs/tests/test_bug_messages_webservice.py	2011-03-17 21:14:13 +0000
@@ -61,7 +61,7 @@
         with person_logged_in(self.admin):
             bug_message = bug_msg_set.getByBugAndMessage(
                 self.bug, self.message)
-            self.assertFalse(bug_message.visible)
+            self.assertFalse(bug_message.message.visible)
 
     def test_random_user_cannot_set_visible(self):
         # Logged in users without privs can't set bug comment

=== modified file 'lib/lp/code/model/tests/test_recipebuilder.py'
--- lib/lp/code/model/tests/test_recipebuilder.py	2010-12-23 01:02:00 +0000
+++ lib/lp/code/model/tests/test_recipebuilder.py	2011-03-17 21:14:13 +0000
@@ -21,7 +21,7 @@
 from zope.security.proxy import removeSecurityProxy
 
 from canonical.testing.layers import (
-    LaunchpadFunctionalLayer,
+    LaunchpadZopelessLayer,
     )
 from lp.buildmaster.enums import BuildFarmJobType
 from lp.buildmaster.interfaces.builder import CannotBuild
@@ -50,7 +50,7 @@
 
 class TestRecipeBuilder(TestCaseWithFactory):
 
-    layer = LaunchpadFunctionalLayer
+    layer = LaunchpadZopelessLayer
 
     def makeJob(self, recipe_registrant=None, recipe_owner=None):
         """Create a sample `ISourcePackageRecipeBuildJob`."""

=== modified file 'lib/lp/registry/adapters.py'
--- lib/lp/registry/adapters.py	2011-01-04 16:08:57 +0000
+++ lib/lp/registry/adapters.py	2011-03-17 21:14:13 +0000
@@ -18,6 +18,9 @@
 from zope.interface import implements
 
 from canonical.launchpad.webapp.interfaces import ILaunchpadPrincipal
+from lp.archivepublisher.interfaces.publisherconfig import (
+    IPublisherConfigSet,
+    )
 from lp.registry.interfaces.poll import (
     IPollSet,
     IPollSubset,
@@ -112,3 +115,10 @@
     or `ILaunchpadUsage`.
     """
     return productseries.product
+
+
+def distribution_to_publisherconfig(distro):
+    """Adapts `IDistribution` to `IPublisherConfig`."""
+    # Used for traversal from distro to +pubconf.
+    config = getUtility(IPublisherConfigSet).getByDistribution(distro)
+    return config

=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml	2011-01-24 20:53:10 +0000
+++ lib/lp/registry/browser/configure.zcml	2011-03-17 21:14:13 +0000
@@ -1948,7 +1948,7 @@
         for="lp.registry.interfaces.distribution.IDistribution"
         permission="launchpad.Edit"
         facet="overview"
-        class="canonical.launchpad.browser.ObjectReassignmentView">
+        class="lp.registry.browser.distribution.DistributionReassignmentView">
         <browser:page
             name="+reassign"
             template="../../../canonical/launchpad/templates/object-reassignment.pt"/>
@@ -1992,6 +1992,13 @@
         facet="overview"
         permission="launchpad.Edit"
         template="../../app/templates/generic-edit.pt"/>
+    <browser:page
+        name="+pubconf"
+        for="lp.registry.interfaces.distribution.IDistribution"
+        class="lp.registry.browser.distribution.DistributionPublisherConfigView"
+        template="../../app/templates/generic-edit.pt"
+        facet="overview"
+        permission="launchpad.Admin"/>
     <browser:defaultView
         for="lp.registry.interfaces.distribution.IDistributionSet"
         name="+index"/>

=== modified file 'lib/lp/registry/browser/distribution.py'
--- lib/lp/registry/browser/distribution.py	2011-03-04 09:55:17 +0000
+++ lib/lp/registry/browser/distribution.py	2011-03-17 21:14:13 +0000
@@ -21,6 +21,8 @@
     'DistributionPPASearchView',
     'DistributionPackageSearchView',
     'DistributionPendingReviewMirrorsView',
+    'DistributionPublisherConfigView',
+    'DistributionReassignmentView',
     'DistributionSeriesView',
     'DistributionSeriesMirrorsRSSView',
     'DistributionSeriesMirrorsView',
@@ -40,6 +42,7 @@
 
 from zope.component import getUtility
 from zope.event import notify
+from zope.formlib import form
 from zope.interface import implements
 from zope.lifecycleevent import ObjectCreatedEvent
 from zope.security.interfaces import Unauthorized
@@ -78,6 +81,10 @@
     )
 from lp.app.errors import NotFoundError
 from lp.app.widgets.image import ImageChangeWidget
+from lp.archivepublisher.interfaces.publisherconfig import (
+    IPublisherConfig,
+    IPublisherConfigSet,
+    )
 from lp.blueprints.browser.specificationtarget import (
     HasSpecificationsMenuMixin,
     )
@@ -92,6 +99,7 @@
     RegistryCollectionActionMenuBase,
     )
 from lp.registry.browser.pillar import PillarBugsMenu
+from lp.registry.browser.objectreassignment import ObjectReassignmentView
 from lp.registry.interfaces.distribution import (
     IDerivativeDistribution,
     IDistribution,
@@ -278,7 +286,12 @@
     """A menu of context actions."""
     usedfor = IDistribution
     facet = 'overview'
-    links = ['edit']
+    links = ['edit', 'pubconf']
+
+    @enabled_with_permission("launchpad.Admin")
+    def pubconf(self):
+        text = "Configure publisher"
+        return Link("+pubconf", text, icon="edit")
 
 
 class DistributionOverviewMenu(ApplicationMenu, DistributionLinksMixin):
@@ -325,7 +338,7 @@
 
     @enabled_with_permission('launchpad.Edit')
     def reassign(self):
-        text = 'Change registrant'
+        text = 'Change maintainer'
         return Link('+reassign', text, icon='edit')
 
     def newmirror(self):
@@ -770,6 +783,7 @@
             domainname=data['domainname'],
             members=data['members'],
             owner=self.user,
+            registrant=self.user,
             )
         notify(ObjectCreatedEvent(distribution))
         self.next_url = canonical_url(distribution)
@@ -1081,3 +1095,62 @@
     @cachedproperty
     def mirrors(self):
         return self.context.disabled_mirrors
+
+
+class DistributionReassignmentView(ObjectReassignmentView):
+    """View class for changing distribution maintainer."""
+    ownerOrMaintainerName = 'maintainer'
+
+
+class DistributionPublisherConfigView(LaunchpadFormView):
+    """View class for configuring publisher options for a DistroSeries.
+
+    It redirects to the main distroseries page after a successful edit.
+    """
+    schema = IPublisherConfig
+    field_names = ['root_dir', 'base_url', 'copy_base_url']
+
+    @property
+    def label(self):
+        """See `LaunchpadFormView`."""
+        return 'Publisher configuration for %s' % self.context.title
+
+    @property
+    def page_title(self):
+        """The page title."""
+        return self.label
+
+    @property
+    def cancel_url(self):
+        """See `LaunchpadFormView`."""
+        return canonical_url(self.context)
+
+    @property
+    def initial_values(self):
+        """If the config already exists, set up the fields with data."""
+        config = getUtility(
+            IPublisherConfigSet).getByDistribution(self.context)
+        values = {}
+        if config is not None:
+            for name in self.field_names:
+                values[name] = getattr(config, name)
+
+        return values
+
+    @action("Save")
+    def save_action(self, action, data):
+        """Update the context and redirect to its overview page."""
+        config = getUtility(IPublisherConfigSet).getByDistribution(
+            self.context)
+        if config is None:
+            config = getUtility(IPublisherConfigSet).new(
+                distribution=self.context,
+                root_dir=data['root_dir'],
+                base_url=data['base_url'],
+                copy_base_url=data['copy_base_url'])
+        else:
+            form.applyChanges(config, self.form_fields, data, self.adapters)
+
+        self.request.response.addInfoNotification(
+            'Your changes have been applied.')
+        self.next_url = canonical_url(self.context)

=== modified file 'lib/lp/registry/browser/distroseries.py'
--- lib/lp/registry/browser/distroseries.py	2011-03-08 11:56:40 +0000
+++ lib/lp/registry/browser/distroseries.py	2011-03-17 21:14:13 +0000
@@ -24,7 +24,6 @@
 from zope.interface import Interface
 from zope.lifecycleevent import ObjectCreatedEvent
 from zope.schema import (
-    Bool,
     Choice,
     List,
     TextLine,
@@ -69,6 +68,7 @@
 from lp.app.widgets.itemswidgets import (
     LabeledMultiCheckBoxWidget,
     LaunchpadDropdownWidget,
+    LaunchpadRadioWidget,
     )
 from lp.blueprints.browser.specificationtarget import (
     HasSpecificationsMenuMixin,
@@ -532,6 +532,23 @@
         return navigator
 
 
+# A simple vocabulary for package filtering on the source package
+# differences page
+NON_BLACKLISTED = 'non-blacklisted'
+BLACKLISTED = 'blacklisted'
+HIGHER_VERSION_THAN_PARENT = 'higher-than-parent'
+
+DEFAULT_PACKAGE_TYPE = NON_BLACKLISTED
+
+PACKAGE_TYPE_VOCABULARY = SimpleVocabulary((
+    SimpleTerm(NON_BLACKLISTED, NON_BLACKLISTED, 'Non blacklisted packages'),
+    SimpleTerm(BLACKLISTED, BLACKLISTED, 'Blacklisted packages'),
+    SimpleTerm(
+        HIGHER_VERSION_THAN_PARENT,
+        HIGHER_VERSION_THAN_PARENT,
+        'Blacklisted packages with a higher version than in the parent')))
+
+
 class DistroSeriesNeedsPackagesView(LaunchpadView):
     """A View to show series package to upstream package relationships."""
 
@@ -551,9 +568,10 @@
     name_filter = TextLine(
         title=_("Package name contains"), required=False)
 
-    include_blacklisted_filter = Bool(
-        title=_("include blacklisted packages"),
-        required=False, default=False)
+    package_type = Choice(
+        vocabulary=PACKAGE_TYPE_VOCABULARY,
+        default=DEFAULT_PACKAGE_TYPE,
+        required=True)
 
     selected_differences = List(
         title=_('Selected differences'),
@@ -565,8 +583,9 @@
 class DistroSeriesLocalDifferences(LaunchpadFormView):
     """Present differences between a derived series and its parent."""
     schema = IDifferencesFormSchema
-    field_names = ['selected_differences']
+    field_names = ['selected_differences', 'package_type']
     custom_widget('selected_differences', LabeledMultiCheckBoxWidget)
+    custom_widget('package_type', LaunchpadRadioWidget)
 
     page_title = 'Local package differences'
 
@@ -582,6 +601,7 @@
                 self.context.parent_series.displayname,
                 self.context.displayname,
                 ))
+
         super(DistroSeriesLocalDifferences, self).initialize()
 
     @property
@@ -662,33 +682,41 @@
             return None
 
     @property
-    def specified_include_blacklisted_filter(self):
-        """If specified, return the 'blacklisted' filter from the GET form
+    def specified_package_type(self):
+        """If specified, return the package type filter from the GET form
         data.
         """
-        include_blacklisted_filter = self.request.query_string_params.get(
-            'field.include_blacklisted_filter')
-
-        if include_blacklisted_filter and include_blacklisted_filter[0]:
-            return include_blacklisted_filter[0]
+        package_type = self.request.query_string_params.get(
+            'field.package_type')
+        if package_type and package_type[0]:
+            return package_type[0]
         else:
-            return None
+            return DEFAULT_PACKAGE_TYPE
 
     @cachedproperty
     def cached_differences(self):
         """Return a batch navigator of filtered results."""
-        if self.specified_include_blacklisted_filter:
-            status=(
-                DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
-                DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
-        else:
+        if self.specified_package_type == NON_BLACKLISTED:
             status=(
                 DistroSeriesDifferenceStatus.NEEDS_ATTENTION,)
+            child_version_higher = False
+        elif self.specified_package_type == BLACKLISTED:
+            status=(
+                DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
+            child_version_higher = False
+        elif self.specified_package_type == HIGHER_VERSION_THAN_PARENT:
+            status=(
+                DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
+            child_version_higher = True
+        else:
+            raise AssertionError('specified_package_type unknown')
+
         differences = getUtility(
             IDistroSeriesDifferenceSource).getForDistroSeries(
                 self.context,
                 source_package_name_filter=self.specified_name_filter,
-                status=status)
+                status=status,
+                child_version_higher=child_version_higher)
         return BatchNavigator(differences, self.request)
 
     @cachedproperty

=== added file 'lib/lp/registry/browser/tests/test_distribution_views.py'
--- lib/lp/registry/browser/tests/test_distribution_views.py	1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/test_distribution_views.py	2011-03-17 21:14:13 +0000
@@ -0,0 +1,167 @@
+# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+import soupmatchers
+from zope.component import getUtility
+
+from canonical.launchpad.ftests import login
+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
+from lp.registry.browser.distribution import DistributionPublisherConfigView
+from lp.registry.interfaces.distribution import IDistributionSet
+from lp.testing import (
+    login_celebrity,
+    TestCaseWithFactory,
+    )
+from lp.testing.sampledata import LAUNCHPAD_ADMIN
+from lp.testing.views import create_initialized_view
+
+
+class TestDistributionPublisherConfigView(TestCaseWithFactory):
+    """Test `DistributionPublisherConfigView`."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        # Create a test distribution.
+        super(TestDistributionPublisherConfigView, self).setUp()
+        self.distro = self.factory.makeDistribution(no_pubconf=True)
+        login(LAUNCHPAD_ADMIN)
+
+        self.ROOT_DIR = u"rootdir/test"
+        self.BASE_URL = u"http://base.url";
+        self.COPY_BASE_URL = u"http://copybase.url";
+
+    def test_empty_initial_values(self):
+        # Test that the page will display empty field values with no
+        # existing config set up.
+        view = DistributionPublisherConfigView(
+            self.distro, LaunchpadTestRequest())
+
+        for value in view.initial_values:
+            self.assertEqual(u"", value)
+
+    def test_previous_initial_values(self):
+        # Test that the initial values are the same as the ones in the
+        # existing database record.
+        pubconf = self.factory.makePublisherConfig(
+            distribution=self.distro)
+
+        view = DistributionPublisherConfigView(
+            self.distro, LaunchpadTestRequest())
+
+        self.assertEqual(pubconf.root_dir, view.initial_values["root_dir"])
+        self.assertEqual(pubconf.base_url, view.initial_values["base_url"])
+        self.assertEqual(
+            pubconf.copy_base_url, view.initial_values["copy_base_url"])
+
+    def _change_and_test_config(self):
+        form = {
+            'field.actions.save': 'save',
+            'field.root_dir': self.ROOT_DIR,
+            'field.base_url': self.BASE_URL,
+            'field.copy_base_url': self.COPY_BASE_URL,
+        }
+
+        view = DistributionPublisherConfigView(
+            self.distro, LaunchpadTestRequest(method='POST', form=form))
+        view.initialize()
+
+        config = getUtility(
+            IPublisherConfigSet).getByDistribution(self.distro)
+
+        self.assertEqual(self.ROOT_DIR, config.root_dir)
+        self.assertEqual(self.BASE_URL, config.base_url)
+        self.assertEqual(self.COPY_BASE_URL, config.copy_base_url)
+
+    def test_add_new_config(self):
+        # Test POSTing a new config.
+        self._change_and_test_config()
+
+    def test_change_existing_config(self):
+        # Test POSTing to change existing config.
+        pubconf = self.factory.makePublisherConfig(
+            distribution=self.distro,
+            root_dir=u"random",
+            base_url=u"blah",
+            copy_base_url=u"foo",
+            )
+        self._change_and_test_config()
+
+
+class TestDistroAddView(TestCaseWithFactory):
+    """Test the +add page for a new distribution."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestDistroAddView, self).setUp()
+        self.owner = self.factory.makePerson()
+        self.registrant = self.factory.makePerson()
+        self.simple_user = self.factory.makePerson()
+
+    def test_registrant_set_by_creation(self):
+        # The registrant field should be set to the Person creating
+        # the distribution.
+        admin = login_celebrity('admin')
+        distributionset = getUtility(IDistributionSet)
+        creation_form = {
+            'field.name': 'newbuntu',
+            'field.displayname': 'newbuntu',
+            'field.title': 'newbuntu',
+            'field.summary': 'newbuntu',
+            'field.description': 'newbuntu',
+            'field.domainname': 'newbuntu',
+            'field.members': self.simple_user.name,
+            'field.actions.save': 'Save',
+            }
+        view = create_initialized_view(
+            distributionset, '+add', principal=admin,
+            method='POST', form=creation_form)
+        distribution = distributionset.getByName('newbuntu')
+        self.assertEqual(distribution.owner, admin)
+        self.assertEqual(distribution.registrant, admin)
+
+
+class TestDistroReassignView(TestCaseWithFactory):
+    """Test the +reassign page for a new distribution."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestDistroReassignView, self).setUp()
+        self.owner = self.factory.makePerson()
+        self.registrant = self.factory.makePerson()
+        self.simple_user = self.factory.makePerson()
+
+    def test_reassign_distro_change_owner_not_registrant(self):
+        # Reassigning a distribution should not change the registrant.
+        admin = login_celebrity('admin')
+        distribution = self.factory.makeDistribution(
+            name="boobuntu", owner=self.owner, registrant=self.registrant)
+        reassign_form = {
+            'field.owner': self.simple_user.name,
+            'field.existing': 'existing',
+            'field.actions.change': 'Change',
+            }
+        view = create_initialized_view(
+            distribution, '+reassign', principal=admin,
+            method='POST', form=reassign_form)
+        self.assertEqual(distribution.owner, self.simple_user)
+        self.assertEqual(distribution.registrant, self.registrant)
+
+    def test_reassign_distro_page_title(self):
+        # Reassign should say maintainer instead of owner.
+        admin = login_celebrity('admin')
+        distribution = self.factory.makeDistribution(
+            name="boobuntu", owner=self.owner, registrant=self.registrant)
+        view = create_initialized_view(
+            distribution, '+reassign', principal=admin, method='GET')
+        header_match = soupmatchers.HTMLContains(
+            soupmatchers.Tag(
+                'Header should say maintainer (not owner)', 'h1',
+                text='Change the maintainer of Boobuntu'))
+        self.assertThat(view.render(), header_match)

=== modified file 'lib/lp/registry/browser/tests/test_series_views.py'
--- lib/lp/registry/browser/tests/test_series_views.py	2011-03-07 19:53:13 +0000
+++ lib/lp/registry/browser/tests/test_series_views.py	2011-03-17 21:14:13 +0000
@@ -38,6 +38,11 @@
     login_person,
     person_logged_in,
     )
+from lp.registry.browser.distroseries import (
+    BLACKLISTED,
+    NON_BLACKLISTED,
+    HIGHER_VERSION_THAN_PARENT,
+    )
 from lp.testing.views import create_initialized_view
 
 
@@ -296,9 +301,38 @@
         self.assertContentEqual(
             [diff2, diff1], unfiltered_view.cached_differences.batch)
 
-    def test_batch_blacklisted(self):
-        # The include_blacklisted_filter parameter allows to list
-        # blacklisted packages.
+    def test_batch_unfiltered(self):
+        # The default filter is all non blacklisted differences.
+        set_derived_series_ui_feature_flag(self)
+        derived_series = self.factory.makeDistroSeries(
+            name='derilucid', parent_series=self.factory.makeDistroSeries(
+                name='lucid'))
+        diff1 = self.factory.makeDistroSeriesDifference(
+            derived_series=derived_series,
+            source_package_name_str="my-src-package")
+        diff2 = self.factory.makeDistroSeriesDifference(
+            derived_series=derived_series,
+            source_package_name_str="my-second-src-package")
+        blacklisted_diff = self.factory.makeDistroSeriesDifference(
+            derived_series=derived_series,
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
+
+        filtered_view = create_initialized_view(
+            derived_series,
+            '+localpackagediffs',
+            query_string='field.package_type=%s' %NON_BLACKLISTED)
+        filtered_view2 = create_initialized_view(
+            derived_series,
+            '+localpackagediffs')
+
+        self.assertContentEqual(
+            [diff2, diff1], filtered_view.cached_differences.batch)
+        self.assertContentEqual(
+            [diff2, diff1], filtered_view2.cached_differences.batch)
+
+    def test_batch_differences_packages(self):
+        # field.package_type parameter allows to list only
+        # blacklisted differences.
         set_derived_series_ui_feature_flag(self)
         derived_series = self.factory.makeDistroSeries(
             name='derilucid', parent_series=self.factory.makeDistroSeries(
@@ -310,7 +344,7 @@
         blacklisted_view = create_initialized_view(
             derived_series,
             '+localpackagediffs',
-            query_string='field.include_blacklisted_filter=on')
+            query_string='field.package_type=%s' %BLACKLISTED)
         unblacklisted_view = create_initialized_view(
             derived_series,
             '+localpackagediffs')
@@ -320,6 +354,36 @@
         self.assertContentEqual(
             [], unblacklisted_view.cached_differences.batch)
 
+    def test_batch_blacklisted_differences_with_higher_version(self):
+        # field.package_type parameter allows to list only
+        # blacklisted differences with a child's version higher than parent's.
+        set_derived_series_ui_feature_flag(self)
+        derived_series = self.factory.makeDistroSeries(
+            name='derilucid', parent_series=self.factory.makeDistroSeries(
+                name='lucid'))
+        blacklisted_diff_higher = self.factory.makeDistroSeriesDifference(
+            derived_series=derived_series,
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
+            versions={'base': '1.1', 'parent': '1.3', 'derived': '1.10'})
+        blacklisted_diff_not_higher = self.factory.makeDistroSeriesDifference(
+            derived_series=derived_series,
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
+            versions={'base': '1.1', 'parent': '1.12', 'derived': '1.10'})
+
+        blacklisted_view = create_initialized_view(
+            derived_series,
+            '+localpackagediffs',
+            query_string='field.package_type=%s' %HIGHER_VERSION_THAN_PARENT)
+        unblacklisted_view = create_initialized_view(
+            derived_series,
+            '+localpackagediffs')
+
+        self.assertContentEqual(
+            [blacklisted_diff_higher],
+            blacklisted_view.cached_differences.batch)
+        self.assertContentEqual(
+            [], unblacklisted_view.cached_differences.batch)
+
     def test_canPerformSync_non_editor(self):
         # Non-editors do not see options to sync.
         derived_series = self.factory.makeDistroSeries(

=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2011-03-10 19:11:04 +0000
+++ lib/lp/registry/configure.zcml	2011-03-17 21:14:13 +0000
@@ -1535,6 +1535,7 @@
                 addAnswerContact
                 removeAnswerContact"/>
 
+
         <!-- IFAQTarget -->
 
         <allow
@@ -1578,6 +1579,12 @@
         for="lp.registry.interfaces.distribution.IDistributionSet"
         factory="lp.registry.browser.distribution.DistributionSetBreadcrumb"
         permission="zope.Public"/>
+    <adapter
+        provides="lp.archivepublisher.interfaces.publisherconfig.IPublisherConfig"
+        for="lp.registry.interfaces.distribution.IDistribution"
+        factory="lp.registry.adapters.distribution_to_publisherconfig"
+        permission="zope.Public"/>
+
 
     <!-- DistributionSet -->
 

=== modified file 'lib/lp/registry/interfaces/distribution.py'
--- lib/lp/registry/interfaces/distribution.py	2011-02-24 15:30:54 +0000
+++ lib/lp/registry/interfaces/distribution.py	2011-03-17 21:14:13 +0000
@@ -209,6 +209,11 @@
         PublicPersonChoice(
             title=_("Owner"), vocabulary='ValidOwner',
             description=_("The distro's owner."), required=True))
+    registrant = exported(
+        PublicPersonChoice(
+            title=_("Registrant"), vocabulary='ValidPersonOrTeam',
+            description=_("The distro's registrant."), required=True,
+            readonly=True))
     date_created = exported(
         Datetime(title=_('Date created'),
                  description=_("The date this distribution was registered.")))
@@ -689,7 +694,7 @@
         """Return the IDistribution with the given name or None."""
 
     def new(name, displayname, title, description, summary, domainname,
-            members, owner, mugshot=None, logo=None, icon=None):
+            members, owner, registrant, mugshot=None, logo=None, icon=None):
         """Create a new distribution."""
 
     def getCurrentSourceReleases(distro_to_source_packagenames):

=== modified file 'lib/lp/registry/interfaces/distroseriesdifference.py'
--- lib/lp/registry/interfaces/distroseriesdifference.py	2011-03-15 18:38:51 +0000
+++ lib/lp/registry/interfaces/distroseriesdifference.py	2011-03-17 21:14:13 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Interface classes for a difference between two distribution series."""
@@ -38,8 +38,8 @@
     )
 from lp.registry.interfaces.distroseries import IDistroSeries
 from lp.registry.interfaces.person import IPerson
+from lp.registry.interfaces.role import IHasOwner
 from lp.registry.interfaces.sourcepackagename import ISourcePackageName
-from lp.registry.interfaces.role import IHasOwner
 from lp.soyuz.interfaces.packagediff import IPackageDiff
 from lp.soyuz.interfaces.publishing import ISourcePackagePublishingHistory
 
@@ -224,7 +224,8 @@
         distro_series,
         difference_type=DistroSeriesDifferenceType.DIFFERENT_VERSIONS,
         source_package_name_filter=None,
-        status=None):
+        status=None,
+        child_version_higher=False):
         """Return differences for the derived distro series.
 
         :param distro_series: The derived distribution series which is to be
@@ -238,6 +239,9 @@
         :param status: Only differences matching the status(es) will be
             included.
         :type status: `DistroSeriesDifferenceStatus`.
+        :param child_version_higher: Only differences for which the child's
+            version is higher than the parent's version will be included.
+        :type child_version_higher: bool.
         :return: A result set of differences.
         """
 

=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py	2011-03-01 05:05:26 +0000
+++ lib/lp/registry/model/distribution.py	2011-03-17 21:14:13 +0000
@@ -243,6 +243,9 @@
     owner = ForeignKey(
         dbName='owner', foreignKey='Person',
         storm_validator=validate_public_person, notNull=True)
+    registrant = ForeignKey(
+        dbName='registrant', foreignKey='Person',
+        storm_validator=validate_public_person, notNull=True)
     bug_supervisor = ForeignKey(
         dbName='bug_supervisor', foreignKey='Person',
         storm_validator=validate_person,
@@ -1882,7 +1885,7 @@
         return pillar
 
     def new(self, name, displayname, title, description, summary, domainname,
-            members, owner, mugshot=None, logo=None, icon=None):
+            members, owner, registrant, mugshot=None, logo=None, icon=None):
         """See `IDistributionSet`."""
         distro = Distribution(
             name=name,
@@ -1894,6 +1897,7 @@
             members=members,
             mirror_admin=owner,
             owner=owner,
+            registrant=registrant,
             mugshot=mugshot,
             logo=logo,
             icon=icon)

=== modified file 'lib/lp/registry/model/distroseriesdifference.py'
--- lib/lp/registry/model/distroseriesdifference.py	2011-03-15 18:38:51 +0000
+++ lib/lp/registry/model/distroseriesdifference.py	2011-03-17 21:14:13 +0000
@@ -11,14 +11,13 @@
 
 from debian.changelog import Changelog
 from lazr.enum import DBItem
+from sqlobject import StringCol
 from storm.expr import Desc
-
 from storm.locals import (
     And,
     Int,
     Reference,
     Storm,
-    Unicode,
     )
 from zope.component import getUtility
 from zope.interface import (
@@ -88,10 +87,10 @@
                     enum=DistroSeriesDifferenceStatus)
     difference_type = DBEnum(name='difference_type', allow_none=False,
                              enum=DistroSeriesDifferenceType)
-    source_version = Unicode(name='source_version', allow_none=True)
-    parent_source_version = Unicode(name='parent_source_version',
-                                    allow_none=True)
-    base_version = Unicode(name='base_version', allow_none=True)
+    source_version = StringCol(dbName='source_version', notNull=False)
+    parent_source_version = StringCol(dbName='parent_source_version',
+                                      notNull=False)
+    base_version = StringCol(dbName='base_version', notNull=False)
 
     @staticmethod
     def new(derived_series, source_package_name):
@@ -117,7 +116,8 @@
         distro_series,
         difference_type=DistroSeriesDifferenceType.DIFFERENT_VERSIONS,
         source_package_name_filter=None,
-        status=None):
+        status=None,
+        child_version_higher=False):
         """See `IDistroSeriesDifferenceSource`."""
         if status is None:
             status = (
@@ -131,12 +131,18 @@
             DistroSeriesDifference.difference_type == difference_type,
             DistroSeriesDifference.status.is_in(status),
         ]
+
         if source_package_name_filter:
             conditions.extend([
                 DistroSeriesDifference.source_package_name ==
                     SourcePackageName.id,
                 SourcePackageName.name == source_package_name_filter])
 
+        if child_version_higher:
+            conditions.extend([
+                DistroSeriesDifference.source_version >
+                    DistroSeriesDifference.parent_source_version])
+
         return IStore(DistroSeriesDifference).find(
             DistroSeriesDifference,
             And(*conditions))

=== modified file 'lib/lp/registry/stories/distribution/xx-distribution-launchpad-usage.txt'
--- lib/lp/registry/stories/distribution/xx-distribution-launchpad-usage.txt	2010-11-30 15:11:48 +0000
+++ lib/lp/registry/stories/distribution/xx-distribution-launchpad-usage.txt	2011-03-17 21:14:13 +0000
@@ -12,6 +12,10 @@
     Traceback (most recent call last):
       ...
     LinkNotFound...
+    >>> user_browser.getLink('Configure publisher')
+    Traceback (most recent call last):
+      ...
+    LinkNotFound...
     >>> user_browser.open('http://launchpad.dev/ubuntu/+edit')
     Traceback (most recent call last):
       ...
@@ -62,12 +66,31 @@
     >>> admin_browser.getControl(name='field.official_malone').value = False
     >>> admin_browser.getControl(name='field.blueprints_usage').value = (
     ... ['UNKNOWN'])
-    >>> admin_browser.getControl(name='field.answers_usage').value = ['UNKNOWN']
+    >>> admin_browser.getControl(
+    ...     name='field.answers_usage').value = ['UNKNOWN']
     >>> admin_browser.getControl('Change', index=3).click()
 
     >>> print admin_browser.url
     http://launchpad.dev/ubuntu
 
+Only administators can configure the publisher for the distribution:
+
+    >>> admin_browser.getLink('Configure publisher').click()
+    >>> print admin_browser.title
+    Publisher configuration for...
+
+    >>> admin_browser.getControl(
+    ...     name='field.root_dir').value = "/tmp/root_dir"
+    >>> admin_browser.getControl(
+    ...     name='field.base_url').value = "http://base.url/";
+    >>> admin_browser.getControl(
+    ...     name='field.copy_base_url').value = "http://copy.base.url/";
+    >>> admin_browser.getControl('Save').click()
+
+    >>> print admin_browser.url
+    http://launchpad.dev/ubuntu
+
+
 enable_bug_expiration and JavaScript
 ====================================
 

=== modified file 'lib/lp/registry/stories/distribution/xx-distribution-overview.txt'
--- lib/lp/registry/stories/distribution/xx-distribution-overview.txt	2010-05-17 20:09:03 +0000
+++ lib/lp/registry/stories/distribution/xx-distribution-overview.txt	2011-03-17 21:14:13 +0000
@@ -89,7 +89,9 @@
 
     >>> print extract_text(
     ...     find_tag_by_id(anon_browser.contents, 'registration'))
-    registered by Ubuntu Team on 2006-10-16
+    Registered by
+    Registry Administrators
+    on 2006-10-16
 
     >>> print extract_text(find_main_content(anon_browser.contents))
     Ubuntu Linux

=== modified file 'lib/lp/registry/stories/webservice/xx-distribution.txt'
--- lib/lp/registry/stories/webservice/xx-distribution.txt	2011-02-13 22:54:48 +0000
+++ lib/lp/registry/stories/webservice/xx-distribution.txt	2011-03-17 21:14:13 +0000
@@ -43,6 +43,7 @@
     name: u'ubuntu'
     official_bug_tags: []
     owner_link: u'http://.../~ubuntu-team'
+    registrant_link: u'http://.../~registry'
     resource_type_link: u'http://.../#distribution'
     security_contact_link: None
     self_link: u'http://.../ubuntu'

=== modified file 'lib/lp/registry/templates/distribution-index.pt'
--- lib/lp/registry/templates/distribution-index.pt	2010-10-10 21:54:16 +0000
+++ lib/lp/registry/templates/distribution-index.pt	2011-03-17 21:14:13 +0000
@@ -11,8 +11,8 @@
     </tal:heading>
 
     <tal:registering metal:fill-slot="registering">
-        registered by
-        <a tal:replace="structure context/owner/fmt:link" />
+        Registered by
+        <a tal:replace="structure context/registrant/fmt:link" />
         <span tal:content="context/date_created/fmt:displaydate"
               tal:attributes="title context/date_created/fmt:datetime"
           >on 2005-01-01</span>

=== modified file 'lib/lp/registry/templates/distroseries-localdifferences.pt'
--- lib/lp/registry/templates/distroseries-localdifferences.pt	2011-03-07 13:43:29 +0000
+++ lib/lp/registry/templates/distroseries-localdifferences.pt	2011-03-17 21:14:13 +0000
@@ -6,6 +6,15 @@
   metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad">
   <body>
+
+    <metal:block fill-slot="head_epilogue">
+      <style type="text/css">
+        .distroseries-localdiff-search-filter input[type="radio"] {
+          margin-left: 0;
+        }
+      </style>
+    </metal:block>
+
     <tal:heading metal:fill-slot="heading">
       <h1 tal:content="view/label">Package differences between ...</h1>
     </tal:heading>

=== modified file 'lib/lp/registry/templates/packagesearch-macros.pt'
--- lib/lp/registry/templates/packagesearch-macros.pt	2011-03-09 23:03:28 +0000
+++ lib/lp/registry/templates/packagesearch-macros.pt	2011-03-17 21:14:13 +0000
@@ -71,12 +71,9 @@
         type="text" name="field.name_filter"
         tal:attributes="value request/field.name_filter|nothing"/>
       <input type="submit" value="Filter" />
-      <br />
-      <input
-        id="field.include_blacklisted_filter"
-        type="checkbox" name="field.include_blacklisted_filter"
-        tal:attributes="checked request/field.include_blacklisted_filter|nothing"/>
-      <label for="field.include_blacklisted_filter">include blacklisted packages</label>
+     <br />
+     <tal:package_type
+       replace="structure view/widgets/package_type" />
     </div>
     <div class="clearfix"></div>
   </form>

=== modified file 'lib/lp/registry/tests/test_distribution.py'
--- lib/lp/registry/tests/test_distribution.py	2011-03-14 12:22:17 +0000
+++ lib/lp/registry/tests/test_distribution.py	2011-03-17 21:14:13 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for Distribution."""
@@ -6,14 +6,22 @@
 __metaclass__ = type
 
 from lazr.lifecycle.snapshot import Snapshot
+import soupmatchers
+from testtools.matchers import (
+    MatchesAny,
+    Not,
+    )
+from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
+from canonical.launchpad.webapp import canonical_url
 from canonical.testing.layers import (
     DatabaseFunctionalLayer,
     LaunchpadFunctionalLayer,
     )
 from lp.registry.errors import NoSuchDistroSeries
 from lp.registry.interfaces.distribution import IDistribution
+from lp.registry.interfaces.person import IPersonSet
 from lp.registry.interfaces.series import SeriesStatus
 from lp.registry.tests.test_distroseries import (
     TestDistroSeriesCurrentSourceReleases,
@@ -22,7 +30,11 @@
 from lp.soyuz.interfaces.distributionsourcepackagerelease import (
     IDistributionSourcePackageRelease,
     )
-from lp.testing import TestCaseWithFactory
+from lp.testing import (
+    login_person,
+    TestCaseWithFactory,
+    )
+from lp.testing.views import create_initialized_view
 
 
 class TestDistribution(TestCaseWithFactory):
@@ -174,3 +186,99 @@
             self.assertFalse(
                 hasattr(snapshot, attribute),
                 "Snapshot should not include %s." % attribute)
+
+
+class TestDistributionPage(TestCaseWithFactory):
+    """A TestCase for the distribution page."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestDistributionPage, self).setUp('foo.bar@xxxxxxxxxxxxx')
+        self.distro = self.factory.makeDistribution(
+            name="distro", displayname=u'distro')
+        self.admin = getUtility(IPersonSet).getByEmail(
+            'admin@xxxxxxxxxxxxx')
+        self.simple_user = self.factory.makePerson()
+
+    def test_distributionpage_addseries_link(self):
+        """ Verify that an admin sees the +addseries link."""
+        login_person(self.admin)
+        view = create_initialized_view(
+            self.distro, '+index', principal=self.admin)
+        series_matches = soupmatchers.HTMLContains(
+            soupmatchers.Tag(
+                'link to add a series', 'a',
+                attrs={'href':
+                    canonical_url(self.distro, view_name='+addseries')},
+                text='Add series'),
+            soupmatchers.Tag(
+                'Series and milestones widget', 'h2',
+                text='Series and milestones'),
+            )
+        self.assertThat(view.render(), series_matches)
+
+    def test_distributionpage_addseries_link_noadmin(self):
+        """Verify that a non-admin does not see the +addseries link
+        nor the series header (since there is no series yet).
+        """
+        login_person(self.simple_user)
+        view = create_initialized_view(
+            self.distro, '+index', principal=self.simple_user)
+        add_series_match = soupmatchers.HTMLContains(
+            soupmatchers.Tag(
+                'link to add a series', 'a',
+                attrs={'href':
+                    canonical_url(self.distro, view_name='+addseries')},
+                text='Add series'))
+        series_header_match = soupmatchers.HTMLContains(
+            soupmatchers.Tag(
+                'Series and milestones widget', 'h2',
+                text='Series and milestones'))
+        self.assertThat(
+            view.render(),
+            Not(MatchesAny(add_series_match, series_header_match)))
+
+    def test_distributionpage_series_list_noadmin(self):
+        """Verify that a non-admin does see the series list
+        when there is a series.
+        """
+        self.factory.makeDistroSeries(distribution=self.distro,
+            status=SeriesStatus.CURRENT)
+        login_person(self.simple_user)
+        view = create_initialized_view(
+            self.distro, '+index', principal=self.simple_user)
+        add_series_match = soupmatchers.HTMLContains(
+            soupmatchers.Tag(
+                'link to add a series', 'a',
+                attrs={'href':
+                    canonical_url(self.distro, view_name='+addseries')},
+                text='Add series'))
+        series_header_match = soupmatchers.HTMLContains(
+            soupmatchers.Tag(
+                'Series and milestones widget', 'h2',
+                text='Series and milestones'))
+        self.assertThat(view.render(), series_header_match)
+        self.assertThat(view.render(), Not(add_series_match))
+
+
+class DistroRegistrantTestCase(TestCaseWithFactory):
+    """A TestCase for registrants and owners of a distribution.
+
+    The registrant is the creator of the distribution (read-only field).
+    The owner is really the maintainer.
+    """
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(DistroRegistrantTestCase, self).setUp()
+        self.owner = self.factory.makePerson()
+        self.registrant = self.factory.makePerson()
+
+    def test_distro_registrant_owner_differ(self):
+        distribution = self.factory.makeDistribution(
+            name="boobuntu", owner=self.owner, registrant=self.registrant)
+        self.assertNotEqual(distribution.owner, distribution.registrant)
+        self.assertEqual(distribution.owner, self.owner)
+        self.assertEqual(distribution.registrant, self.registrant)

=== modified file 'lib/lp/registry/tests/test_distributionsourcepackage.py'
--- lib/lp/registry/tests/test_distributionsourcepackage.py	2010-10-04 19:50:45 +0000
+++ lib/lp/registry/tests/test_distributionsourcepackage.py	2011-03-17 21:14:13 +0000
@@ -32,7 +32,8 @@
         distribution = distribution_set.new(name='wart',
             displayname='wart', title='wart', description='lots of warts',
             summary='lots of warts', domainname='wart.dumb',
-            members=self.factory.makeTeam(), owner=self.factory.makePerson())
+            members=self.factory.makeTeam(), owner=self.factory.makePerson(),
+            registrant=self.factory.makePerson())
         naked_distribution = removeSecurityProxy(distribution)
         self.factory.makeSourcePackage(distroseries=distribution)
         dsp = naked_distribution.getSourcePackage(name='pmount')

=== modified file 'lib/lp/registry/tests/test_pillarname_triggers.py'
--- lib/lp/registry/tests/test_pillarname_triggers.py	2010-10-04 19:50:45 +0000
+++ lib/lp/registry/tests/test_pillarname_triggers.py	2011-03-17 21:14:13 +0000
@@ -46,12 +46,12 @@
         # Inserting a new Distribution will populate PillarName
         cur.execute("""
             INSERT INTO Distribution (
-                name, description, domainname, owner, displayname,
-                summary, title, members, mirror_admin
+                name, description, domainname, owner, registrant,
+                displayname, summary, title, members, mirror_admin
                 )
                 VALUES (
-                    'whatever', 'whatever', 'whatever', 1, 'whatever',
-                    'whatever', 'whatever', 1, 1
+                    'whatever', 'whatever', 'whatever', 1, 1,
+                    'whatever', 'whatever', 'whatever', 1, 1
                     )
             """)
         self.failUnless(is_in_sync('whatever'))
@@ -69,7 +69,8 @@
             """)
         self.failUnless(is_in_sync('whatever2'))
 
-        # Deleting a Distribution removes the corresponding entry in PillarName
+        # Deleting a Distribution removes the corresponding entry in
+        # PillarName
         cur.execute("DELETE FROM Distribution WHERE name='whatever2'")
         cur.execute("SELECT COUNT(*) FROM PillarName WHERE name='whatever2'")
         self.failUnlessEqual(cur.fetchone()[0], 0)
@@ -101,7 +102,8 @@
 
         # Inserting a new Product will populate PillarName
         cur.execute("""
-            INSERT INTO Product (owner, registrant, name, displayname, title, summary)
+            INSERT INTO Product (
+                owner, registrant, name, displayname, title, summary)
             VALUES (
                 1, 1, 'whatever', 'whatever', 'whatever', 'whatever'
                 )
@@ -154,7 +156,8 @@
         # Inserting a new ProjectGroup will populate PillarName
         cur.execute("""
             INSERT INTO Project (
-                name, owner, registrant, displayname, title, summary, description
+                name, owner, registrant, displayname, title, summary,
+                description
                 )
                 VALUES (
                     'whatever', 1, 1, 'whatever', 'whatever',

=== modified file 'lib/lp/scripts/garbo.py'
--- lib/lp/scripts/garbo.py	2011-03-09 23:43:02 +0000
+++ lib/lp/scripts/garbo.py	2011-03-17 21:14:13 +0000
@@ -86,6 +86,7 @@
     LaunchpadCronScript,
     SilentLaunchpadScriptFailure,
     )
+from lp.services.session.model import SessionData
 from lp.soyuz.model.files import SourcePackageReleaseFile
 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
 from lp.translations.interfaces.potemplate import IPOTemplateSet
@@ -125,10 +126,14 @@
     # from. Must be overridden.
     target_table_class = None
 
-    # The column name in target_table we use as the integer key. May be
-    # overridden.
+    # The column name in target_table we use as the key. The type must
+    # match that returned by the ids_to_prune_query and the
+    # target_table_key_type. May be overridden.
     target_table_key = 'id'
 
+    # SQL type of the target_table_key. May be overridden.
+    target_table_key_type = 'integer'
+
     # An SQL query returning a list of ids to remove from target_table.
     # The query must return a single column named 'id' and should not
     # contain duplicates. Must be overridden.
@@ -137,10 +142,17 @@
     # See `TunableLoop`. May be overridden.
     maximum_chunk_size = 10000
 
+    def getStore(self):
+        """The master Store for the table we are pruning.
+
+        May be overridden.
+        """
+        return IMasterStore(self.target_table_class)
+
     def __init__(self, log, abort_time=None):
         super(BulkPruner, self).__init__(log, abort_time)
 
-        self.store = IMasterStore(self.target_table_class)
+        self.store = self.getStore()
         self.target_table_name = self.target_table_class.__storm_table__
 
         # Open the cursor.
@@ -157,11 +169,14 @@
     def __call__(self, chunk_size):
         """See `ITunableLoop`."""
         result = self.store.execute("""
-            DELETE FROM %s WHERE %s IN (
+            DELETE FROM %s
+            WHERE %s IN (
                 SELECT id FROM
-                cursor_fetch('bulkprunerid', %d) AS f(id integer))
+                cursor_fetch('bulkprunerid', %d) AS f(id %s))
             """
-            % (self.target_table_name, self.target_table_key, chunk_size))
+            % (
+                self.target_table_name, self.target_table_key,
+                chunk_size, self.target_table_key_type))
         self._num_removed = result.rowcount
         transaction.commit()
 
@@ -175,9 +190,7 @@
 
     XXX bug=723596 StuartBishop: This job only needs to run once per month.
     """
-
     target_table_class = POTranslation
-
     ids_to_prune_query = """
         SELECT POTranslation.id AS id FROM POTranslation
         EXCEPT (
@@ -204,6 +217,68 @@
         """
 
 
+class SessionPruner(BulkPruner):
+    """Base class for session removal."""
+
+    target_table_class = SessionData
+    target_table_key = 'client_id'
+    target_table_key_type = 'text'
+
+
+class AntiqueSessionPruner(SessionPruner):
+    """Remove sessions not accessed for 60 days"""
+
+    ids_to_prune_query = """
+        SELECT client_id AS id FROM SessionData
+        WHERE last_accessed < CURRENT_TIMESTAMP - CAST('60 days' AS interval)
+        """
+
+
+class UnusedSessionPruner(SessionPruner):
+    """Remove sessions older than 1 day with no authentication credentials."""
+
+    ids_to_prune_query = """
+        SELECT client_id AS id FROM SessionData
+        WHERE
+            last_accessed < CURRENT_TIMESTAMP - CAST('1 day' AS interval)
+            AND client_id NOT IN (
+                SELECT client_id
+                FROM SessionPkgData
+                WHERE
+                    product_id = 'launchpad.authenticateduser'
+                    AND key='logintime')
+        """
+
+
+class DuplicateSessionPruner(SessionPruner):
+    """Remove all but the most recent 6 authenticated sessions for a user.
+
+    We sometimes see users with dozens or thousands of authenticated
+    sessions. To limit exposure to replay attacks, we remove all but
+    the most recent 6 of them for a given user.
+    """
+
+    ids_to_prune_query = """
+        SELECT client_id AS id
+        FROM (
+            SELECT
+                sessiondata.client_id,
+                last_accessed,
+                rank() OVER pickle AS rank
+            FROM SessionData, SessionPkgData
+            WHERE
+                SessionData.client_id = SessionPkgData.client_id
+                AND product_id = 'launchpad.authenticateduser'
+                AND key='accountid'
+            WINDOW pickle AS (PARTITION BY pickle ORDER BY last_accessed DESC)
+            ) AS whatever
+        WHERE
+            rank > 6
+            AND last_accessed < CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
+                - CAST('1 hour' AS interval)
+        """
+
+
 class OAuthNoncePruner(TunableLoop):
     """An ITunableLoop to prune old OAuthNonce records.
 
@@ -1113,6 +1188,9 @@
         RevisionCachePruner,
         BugHeatUpdater,
         BugWatchScheduler,
+        AntiqueSessionPruner,
+        UnusedSessionPruner,
+        DuplicateSessionPruner,
         PopulateSPRChangelogs,
         ]
     experimental_tunable_loops = []

=== modified file 'lib/lp/scripts/tests/test_garbo.py'
--- lib/lp/scripts/tests/test_garbo.py	2011-03-09 23:43:02 +0000
+++ lib/lp/scripts/tests/test_garbo.py	2011-03-17 21:14:13 +0000
@@ -20,7 +20,10 @@
     Min,
     SQL,
     )
-from storm.locals import Storm, Int
+from storm.locals import (
+    Int,
+    Storm,
+    )
 from storm.store import Store
 import transaction
 from zope.component import getUtility
@@ -74,13 +77,20 @@
     PersonCreationRationale,
     )
 from lp.scripts.garbo import (
+    AntiqueSessionPruner,
     BulkPruner,
     DailyDatabaseGarbageCollector,
+    DuplicateSessionPruner,
     HourlyDatabaseGarbageCollector,
     OpenIDConsumerAssociationPruner,
+    UnusedSessionPruner,
     )
 from lp.services.job.model.job import Job
 from lp.services.log.logger import BufferLogger
+from lp.services.session.model import (
+    SessionData,
+    SessionPkgData,
+    )
 from lp.soyuz.enums import PackagePublishingStatus
 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
 from lp.testing import (
@@ -195,6 +205,153 @@
             pruner(chunk_size)
 
 
+class TestSessionPruner(TestCase):
+    layer = ZopelessDatabaseLayer
+
+    def setUp(self):
+        super(TestCase, self).setUp()
+
+        # Session database isn't reset between tests. We need to do this
+        # manually.
+        nuke_all_sessions = IMasterStore(SessionData).find(SessionData).remove
+        nuke_all_sessions()
+        self.addCleanup(nuke_all_sessions)
+
+        recent = datetime.now(UTC)
+        yesterday = recent - timedelta(days=1)
+        ancient = recent - timedelta(days=61)
+
+        self.make_session(u'recent_auth', recent, 'auth1')
+        self.make_session(u'recent_unauth', recent, False)
+        self.make_session(u'yesterday_auth', yesterday, 'auth2')
+        self.make_session(u'yesterday_unauth', yesterday, False)
+        self.make_session(u'ancient_auth', ancient, 'auth3')
+        self.make_session(u'ancient_unauth', ancient, False)
+
+    def make_session(self, client_id, accessed, authenticated=None):
+        session_data = SessionData()
+        session_data.client_id = client_id
+        session_data.last_accessed = accessed
+        IMasterStore(SessionData).add(session_data)
+
+        if authenticated:
+            # Add login time information.
+            session_pkg_data = SessionPkgData()
+            session_pkg_data.client_id = client_id
+            session_pkg_data.product_id = u'launchpad.authenticateduser'
+            session_pkg_data.key = u'logintime'
+            session_pkg_data.pickle = 'value is ignored'
+            IMasterStore(SessionPkgData).add(session_pkg_data)
+
+            # Add authenticated as information.
+            session_pkg_data = SessionPkgData()
+            session_pkg_data.client_id = client_id
+            session_pkg_data.product_id = u'launchpad.authenticateduser'
+            session_pkg_data.key = u'accountid'
+            # Normally Account.id, but the session pruning works
+            # at the SQL level and doesn't unpickle anything.
+            session_pkg_data.pickle = authenticated
+            IMasterStore(SessionPkgData).add(session_pkg_data)
+
+    def sessionExists(self, client_id):
+        store = IMasterStore(SessionData)
+        return not store.find(
+            SessionData, SessionData.client_id == client_id).is_empty()
+
+    def test_antique_session_pruner(self):
+        chunk_size = 2
+        log = BufferLogger()
+        pruner = AntiqueSessionPruner(log)
+        try:
+            while not pruner.isDone():
+                pruner(chunk_size)
+        finally:
+            pruner.cleanUp()
+
+        expected_sessions = set([
+            u'recent_auth',
+            u'recent_unauth',
+            u'yesterday_auth',
+            u'yesterday_unauth',
+            # u'ancient_auth',
+            # u'ancient_unauth',
+            ])
+
+        found_sessions = set(
+            IMasterStore(SessionData).find(SessionData.client_id))
+
+        self.assertEqual(expected_sessions, found_sessions)
+
+    def test_unused_session_pruner(self):
+        chunk_size = 2
+        log = BufferLogger()
+        pruner = UnusedSessionPruner(log)
+        try:
+            while not pruner.isDone():
+                pruner(chunk_size)
+        finally:
+            pruner.cleanUp()
+
+        expected_sessions = set([
+            u'recent_auth',
+            u'recent_unauth',
+            u'yesterday_auth',
+            # u'yesterday_unauth',
+            u'ancient_auth',
+            # u'ancient_unauth',
+            ])
+
+        found_sessions = set(
+            IMasterStore(SessionData).find(SessionData.client_id))
+
+        self.assertEqual(expected_sessions, found_sessions)
+
+    def test_duplicate_session_pruner(self):
+        # None of the sessions created in setUp() are duplicates, so
+        # they will all survive the pruning.
+        expected_sessions = set([
+            u'recent_auth',
+            u'recent_unauth',
+            u'yesterday_auth',
+            u'yesterday_unauth',
+            u'ancient_auth',
+            u'ancient_unauth',
+            ])
+
+        now = datetime.now(UTC)
+
+        # Make some duplicate logins from a few days ago.
+        # Only the most recent 6 will be kept. Oldest is 'old dupe 9',
+        # most recent 'old dupe 1'.
+        for count in range(1, 10):
+            self.make_session(
+                u'old dupe %d' % count,
+                now - timedelta(days=2, seconds=count),
+                'old dupe')
+        for count in range(1, 7):
+            expected_sessions.add(u'old dupe %d' % count)
+
+        # Make some other duplicate logins less than an hour old.
+        # All of these will be kept.
+        for count in range(1, 10):
+            self.make_session(u'new dupe %d' % count, now, 'new dupe')
+            expected_sessions.add(u'new dupe %d' % count)
+
+        chunk_size = 2
+        log = BufferLogger()
+        pruner = DuplicateSessionPruner(log)
+        try:
+            while not pruner.isDone():
+                pruner(chunk_size)
+        finally:
+            pruner.cleanUp()
+
+        found_sessions = set(
+            IMasterStore(SessionData).find(SessionData.client_id))
+
+        self.assertEqual(expected_sessions, found_sessions)
+
+
 class TestGarbo(TestCaseWithFactory):
     layer = LaunchpadZopelessLayer
 
@@ -223,7 +380,7 @@
         return collector
 
     def test_OAuthNoncePruner(self):
-        now = datetime.utcnow().replace(tzinfo=UTC)
+        now = datetime.now(UTC)
         timestamps = [
             now - timedelta(days=2), # Garbage
             now - timedelta(days=1) - timedelta(seconds=60), # Garbage
@@ -301,7 +458,7 @@
         self.failUnless(earliest >= now - 24*60*60, 'Still have old nonces')
 
     def test_CodeImportResultPruner(self):
-        now = datetime.utcnow().replace(tzinfo=UTC)
+        now = datetime.now(UTC)
         store = IMasterStore(CodeImportResult)
 
         results_to_keep_count = (
@@ -358,7 +515,7 @@
             >= now - timedelta(days=30))
 
     def test_CodeImportEventPruner(self):
-        now = datetime.utcnow().replace(tzinfo=UTC)
+        now = datetime.now(UTC)
         store = IMasterStore(CodeImportResult)
 
         LaunchpadZopelessLayer.switchDbUser('testadmin')

=== modified file 'lib/lp/scripts/utilities/sanitizedb.py'
--- lib/lp/scripts/utilities/sanitizedb.py	2011-02-21 00:06:55 +0000
+++ lib/lp/scripts/utilities/sanitizedb.py	2011-03-17 21:14:13 +0000
@@ -13,7 +13,10 @@
 import subprocess
 import sys
 
-from storm.locals import Or
+from storm.expr import (
+    Join,
+    Or,
+    )
 import transaction
 from zope.component import getUtility
 
@@ -254,8 +257,14 @@
     def removePrivateBugMessages(self):
         """Remove all hidden bug messages."""
         from lp.bugs.model.bugmessage import BugMessage
+        from canonical.launchpad.database.message import Message
+        message_ids = list(self.store.using(*[
+            BugMessage,
+            Join(Message, BugMessage.messageID == Message.id),
+            ]).find(BugMessage.id, Message.visible == False))
+        self.store.flush()
         count = self.store.find(
-            BugMessage, BugMessage.visible == False).remove()
+            BugMessage, BugMessage.id.is_in(message_ids)).remove()
         self.store.flush()
         self.logger.info("Removed %d private bug messages.", count)
 
@@ -578,7 +587,11 @@
         # deletes because they fail (attempting to change a mutating table).
         # We can repair these caches by forcing the triggers to run for
         # every row.
-        self.store.execute("UPDATE BugMessage SET visible=visible")
+        self.store.execute("""
+            UPDATE Message SET visible=visible
+            FROM BugMessage
+            WHERE BugMessage.message = Message.id
+            """)
 
     def _fail(self, error_message):
         self.logger.fatal(error_message)

=== modified file 'lib/lp/services/configure.zcml'
--- lib/lp/services/configure.zcml	2011-03-07 16:32:12 +0000
+++ lib/lp/services/configure.zcml	2011-03-17 21:14:13 +0000
@@ -16,5 +16,6 @@
   <include package=".profile" />
   <include package=".salesforce" />
   <include package=".scripts" />
+  <include package=".session" />
   <include package=".worlddata" />
 </configure>

=== added directory 'lib/lp/services/session'
=== added file 'lib/lp/services/session/__init__.py'
=== added file 'lib/lp/services/session/adapters.py'
--- lib/lp/services/session/adapters.py	1970-01-01 00:00:00 +0000
+++ lib/lp/services/session/adapters.py	2011-03-17 21:14:13 +0000
@@ -0,0 +1,40 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Session adapters."""
+
+__metaclass__ = type
+__all__ = []
+
+
+from zope.component import adapter
+from zope.interface import implementer
+
+from canonical.database.sqlbase import session_store
+from canonical.launchpad.interfaces.lpstorm import (
+    IMasterStore,
+    ISlaveStore,
+    IStore,
+    )
+from lp.services.session.interfaces import IUseSessionStore
+
+
+@adapter(IUseSessionStore)
+@implementer(IMasterStore)
+def session_master_store(cls):
+    """Adapt a Session database object to an `IMasterStore`."""
+    return session_store()
+
+
+@adapter(IUseSessionStore)
+@implementer(ISlaveStore)
+def session_slave_store(cls):
+    """Adapt a Session database object to an `ISlaveStore`."""
+    return session_store()
+
+
+@adapter(IUseSessionStore)
+@implementer(IStore)
+def session_default_store(cls):
+    """Adapt an Session database object to an `IStore`."""
+    return session_store()

=== added file 'lib/lp/services/session/configure.zcml'
--- lib/lp/services/session/configure.zcml	1970-01-01 00:00:00 +0000
+++ lib/lp/services/session/configure.zcml	2011-03-17 21:14:13 +0000
@@ -0,0 +1,12 @@
+<!-- Copyright 2011 Canonical Ltd.  This software is licensed under the
+     GNU Affero General Public License version 3 (see the file LICENSE).
+-->
+<configure
+    xmlns="http://namespaces.zope.org/zope";
+    xmlns:browser="http://namespaces.zope.org/browser";
+    xmlns:i18n="http://namespaces.zope.org/i18n";
+    i18n_domain="launchpad">
+    <adapter factory=".adapters.session_master_store" />
+    <adapter factory=".adapters.session_slave_store" />
+    <adapter factory=".adapters.session_default_store" />
+</configure>

=== added file 'lib/lp/services/session/interfaces.py'
--- lib/lp/services/session/interfaces.py	1970-01-01 00:00:00 +0000
+++ lib/lp/services/session/interfaces.py	2011-03-17 21:14:13 +0000
@@ -0,0 +1,15 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Session interfaces."""
+
+__metaclass__ = type
+__all__ = ['IUseSessionStore']
+
+
+from zope.interface import Interface
+
+
+class IUseSessionStore(Interface):
+    """Marker interface for Session Storm database classes and instances."""
+    pass

=== added file 'lib/lp/services/session/model.py'
--- lib/lp/services/session/model.py	1970-01-01 00:00:00 +0000
+++ lib/lp/services/session/model.py	2011-03-17 21:14:13 +0000
@@ -0,0 +1,47 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Session Storm database classes"""
+
+__metaclass__ = type
+__all__ = ['SessionData', 'SessionPkgData']
+
+from storm.locals import (
+    Pickle,
+    Storm,
+    Unicode,
+    )
+from zope.interface import (
+    classProvides,
+    implements,
+    )
+
+from canonical.database.datetimecol import UtcDateTimeCol
+from lp.services.session.interfaces import IUseSessionStore
+
+
+class SessionData(Storm):
+    """A user's Session."""
+
+    classProvides(IUseSessionStore)
+    implements(IUseSessionStore)
+
+    __storm_table__ = 'SessionData'
+    client_id = Unicode(primary=True)
+    created = UtcDateTimeCol()
+    last_accessed = UtcDateTimeCol()
+
+
+class SessionPkgData(Storm):
+    """Data storage for a Session."""
+
+    classProvides(IUseSessionStore)
+    implements(IUseSessionStore)
+
+    __storm_table__ = 'SessionPkgData'
+    __storm_primary__ = 'client_id', 'product_id', 'key'
+
+    client_id = Unicode()
+    product_id = Unicode()
+    key = Unicode()
+    pickle = Pickle()

=== added directory 'lib/lp/services/session/tests'
=== added file 'lib/lp/services/session/tests/__init__.py'
=== added file 'lib/lp/services/session/tests/test_session.py'
--- lib/lp/services/session/tests/test_session.py	1970-01-01 00:00:00 +0000
+++ lib/lp/services/session/tests/test_session.py	2011-03-17 21:14:13 +0000
@@ -0,0 +1,32 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Session tests."""
+
+__metaclass__ = type
+
+from canonical.launchpad.interfaces.lpstorm import (
+    IMasterStore,
+    ISlaveStore,
+    IStore,
+    )
+from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.services.session.model import (
+    SessionData,
+    SessionPkgData,
+    )
+from lp.testing import TestCase
+
+
+class TestSessionModelAdapters(TestCase):
+    layer = DatabaseFunctionalLayer
+
+    def test_adapters(self):
+        for adapter in [IMasterStore, ISlaveStore, IStore]:
+            for cls in [SessionData, SessionPkgData]:
+                for obj in [cls, cls()]:
+                    store = adapter(obj)
+                    self.assert_(
+                        'session' in store.get_database()._dsn,
+                        'Unknown store returned adapting %r to %r'
+                        % (obj, adapter))

=== modified file 'lib/lp/soyuz/doc/archive-dependencies.txt'
--- lib/lp/soyuz/doc/archive-dependencies.txt	2010-10-18 12:35:41 +0000
+++ lib/lp/soyuz/doc/archive-dependencies.txt	2011-03-17 21:14:13 +0000
@@ -226,11 +226,11 @@
     0
 
     >>> print_building_sources_list(a_build)
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse
 
 Once we publish a test binary in Celso's PPA hoary/i386,
@@ -243,11 +243,11 @@
 
     >>> print_building_sources_list(a_build)
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse
 
 Similarly, unpopulated PPA dependencies are *not* listed in the building
@@ -259,11 +259,11 @@
     ...     getUtility(IComponentSet)['main'])
     >>> print_building_sources_list(a_build)
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse
 
 But *populated* PPA dependencies *are* listed in the building 'sources_list'.
@@ -274,11 +274,11 @@
     >>> print_building_sources_list(a_build)
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
     deb http://ppa.launchpad.dev/mark/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse
 
 The authentication information gets added for private PPA
@@ -300,11 +300,11 @@
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
     deb http://buildd:sekrit@xxxxxxxxxxxxxxxxxxxxxxxxx/mark/p3a/ubuntu
         hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse
 
 Remove the private PPA dependency before continuing.
@@ -332,11 +332,11 @@
     ValueError: incomplete format
     <BLANKLINE>
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse
 
 However, in order to avoid the problem going forward (and to allow the PPA
@@ -372,11 +372,11 @@
     ValueError: incomplete format
     <BLANKLINE>
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse
 
 However, in order to avoid the problem going forward (and to allow the PPA
@@ -407,11 +407,11 @@
 
     >>> print_building_sources_list(a_build)
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse
 
 The default build behaviour will remain unchanged when we override the
@@ -423,11 +423,11 @@
 
     >>> print_building_sources_list(a_build)
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse
 
     >>> cprov.archive.removeArchiveDependency(ubuntu.main_archive)
@@ -450,9 +450,9 @@
 
     >>> print_building_sources_list(a_build)
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
+    deb http://archive.launchpad.dev/ubuntu hoary
         main universe
-    deb http://ftpmaster.internal/ubuntu hoary-security
+    deb http://archive.launchpad.dev/ubuntu hoary-security
         main universe
 
     >>> cprov.archive.removeArchiveDependency(ubuntu.main_archive)
@@ -467,7 +467,7 @@
 
     >>> print_building_sources_list(a_build)
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary main restricted
+    deb http://archive.launchpad.dev/ubuntu hoary main restricted
 
     >>> cprov.archive.removeArchiveDependency(ubuntu.main_archive)
 
@@ -480,13 +480,13 @@
 
     >>> print_building_sources_list(a_build)
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-proposed
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-proposed
         main restricted universe multiverse
 
     >>> cprov.archive.removeArchiveDependency(ubuntu.main_archive)
@@ -499,13 +499,13 @@
 
     >>> print_building_sources_list(a_build)
     deb http://ppa.launchpad.dev/cprov/ppa/ubuntu hoary main
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-backports
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-backports
         main restricted universe multiverse
 
     >>> cprov.archive.removeArchiveDependency(ubuntu.main_archive)
@@ -532,12 +532,12 @@
     >>> [partner_build] = pub_source.createMissingBuilds()
 
     >>> print_building_sources_list(partner_build)
-    deb http://ftpmaster.internal/ubuntu-partner hoary partner
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu-partner hoary partner
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse
 
 
@@ -570,9 +570,9 @@
     deb http://user:pass@repository zoing everything
     deb http://user:pass@repository hoary public private
     deb http://user:pass@repository hoary-extra public
-    deb http://ftpmaster.internal/ubuntu hoary
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-security
-        main restricted universe multiverse
-    deb http://ftpmaster.internal/ubuntu hoary-updates
+    deb http://archive.launchpad.dev/ubuntu hoary
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-security
+        main restricted universe multiverse
+    deb http://archive.launchpad.dev/ubuntu hoary-updates
         main restricted universe multiverse

=== modified file 'lib/lp/soyuz/doc/archive.txt'
--- lib/lp/soyuz/doc/archive.txt	2011-03-03 00:43:44 +0000
+++ lib/lp/soyuz/doc/archive.txt	2011-03-17 21:14:13 +0000
@@ -1284,21 +1284,23 @@
     None
 
 IArchive.archive_url will return a URL for the archive that the builder can
-use to retrieve files from it.
+use to retrieve files from it.  Internal paths and urls supplied via the
+PunlisherConfig require us to log in as an admin:
 
+    >>> login('admin@xxxxxxxxxxxxx')
     >>> print partner_archive.archive_url
-    http://ftpmaster.internal/ubuntutest-partner
+    http://archive.launchpad.dev/ubuntutest-partner
 
     >>> print sandbox_archive.archive_url
     http://ppa.launchpad.dev/name16/ppa/ubuntu
 
     >>> print getUtility(IArchiveSet).getByDistroPurpose(
     ...     ubuntutest, ArchivePurpose.PRIMARY).archive_url
-    http://ftpmaster.internal/ubuntutest
+    http://archive.launchpad.dev/ubuntutest
 
     >>> print getUtility(IArchiveSet).getByDistroPurpose(
     ...     ubuntu, ArchivePurpose.DEBUG).archive_url
-    http://ftpmaster.internal/ubuntu-debug
+    http://archive.launchpad.dev/ubuntu-debug
 
 COPY archives use a URL format of <distro-name>-<archive-name>:
 
@@ -1310,6 +1312,7 @@
 If the archive is private, the url may be different as private PPAs
 are published to a secure location.
 
+    >>> login("celso.providelo@xxxxxxxxxxxxx")
     >>> print cprov_archive.archive_url
     http://ppa.launchpad.dev/cprov/ppa/ubuntu
 
@@ -2460,7 +2463,7 @@
     >>> uber = getUtility(IDistributionSet).new(
     ... 'uberdistro', 'The uberdistro', 'The mother of all distros',
     ... 'All you would want from a distro', 'zero', 'uberdistro.org',
-    ... mark, cprov)
+    ... mark, cprov, cprov)
 
 The primary archive for the Ãœberdistro was created by the
 IDistributionSet.new() method. Let's check its publish flag.

=== modified file 'lib/lp/soyuz/doc/publishing.txt'
--- lib/lp/soyuz/doc/publishing.txt	2011-03-03 00:43:44 +0000
+++ lib/lp/soyuz/doc/publishing.txt	2011-03-17 21:14:13 +0000
@@ -362,7 +362,7 @@
     >>> [(pub_file.libraryfilealias.filename, pub_file.file_type_name,
     ...   pub_file.archive_url) for pub_file in spph.files]
     [(u'alsa-utils_1.0.8-1ubuntu1.dsc', 'dsc',
-      u'http://ftpmaster.internal/ubuntu/pool/main/a/alsa-utils/alsa-utils_1.0.8-1ubuntu1.dsc')]
+      u'http://archive.launchpad.dev/ubuntu/pool/main/a/alsa-utils/alsa-utils_1.0.8-1ubuntu1.dsc')]
 
 
 Deletion and obsolescence
@@ -952,8 +952,11 @@
     >>> [pub_file.libraryfilealias.filename for pub_file in bpph.files]
     [u'mozilla-firefox_0.9_i386.deb']
 
+    >>> debian = bpph.distroarchseries.distroseries.distribution
+    >>> discard = factory.makePublisherConfig(
+    ...     distribution=debian, base_url=u"http://archive.launchpad.dev";)
     >>> [pub_file.archive_url for pub_file in bpph.files]
-    [u'http://ftpmaster.internal/debian/pool/universe/m/mozilla-firefox/mozilla-firefox_0.9_i386.deb']
+    [u'http://archive.launchpad.dev/debian/pool/universe/m/mozilla-firefox/mozilla-firefox_0.9_i386.deb']
 
 Binary publishing records also have a download count, which contains
 the number of downloads of this binary package release in this archive.

=== modified file 'lib/lp/soyuz/interfaces/distroarchseries.py'
--- lib/lp/soyuz/interfaces/distroarchseries.py	2010-10-06 11:46:51 +0000
+++ lib/lp/soyuz/interfaces/distroarchseries.py	2011-03-17 21:14:13 +0000
@@ -30,6 +30,7 @@
     )
 
 from canonical.launchpad import _
+from lp.app.validators.name import name_validator
 from lp.registry.interfaces.distroseries import IDistroSeries
 from lp.registry.interfaces.person import IPerson
 from lp.registry.interfaces.role import IHasOwner
@@ -56,7 +57,8 @@
                 "identifies this architecture. All binary packages in the "
                 "archive will use this tag in their filename. Please get it "
                 "correct. It should really never be changed!"),
-            required=True),
+            required=True,
+            constraint=name_validator),
         exported_as="architecture_tag")
     official = exported(
         Bool(

=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py	2011-03-07 07:05:39 +0000
+++ lib/lp/soyuz/model/archive.py	2011-03-17 21:14:13 +0000
@@ -39,7 +39,6 @@
     alsoProvides,
     implements,
     )
-from zope.security.proxy import removeSecurityProxy
 
 from canonical.config import config
 from canonical.database.constants import UTC_NOW
@@ -76,6 +75,7 @@
 from lp.app.errors import NotFoundError
 from lp.app.validators.name import valid_name
 from lp.archivepublisher.debversion import Version
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.archiveuploader.utils import (
     re_isadeb,
     re_issource,
@@ -423,9 +423,11 @@
                 url, "/".join(
                     (self.owner.name, self.name, self.distribution.name)))
 
+        db_pubconf = getUtility(
+            IPublisherConfigSet).getByDistribution(self.distribution)
         if self.is_copy:
             url = urlappend(
-                config.archivepublisher.copy_base_url,
+                db_pubconf.copy_base_url,
                 self.distribution.name + '-' + self.name)
             return urlappend(url, self.distribution.name)
 
@@ -435,8 +437,7 @@
             raise AssertionError(
                 "archive_url unknown for purpose: %s" % self.purpose)
         return urlappend(
-            config.archivepublisher.base_url,
-            self.distribution.name + postfix)
+            db_pubconf.base_url, self.distribution.name + postfix)
 
     @property
     def signing_key_fingerprint(self):

=== modified file 'lib/lp/soyuz/scripts/tests/test_publishdistro.py'
--- lib/lp/soyuz/scripts/tests/test_publishdistro.py	2010-12-20 03:21:03 +0000
+++ lib/lp/soyuz/scripts/tests/test_publishdistro.py	2011-03-17 21:14:13 +0000
@@ -17,6 +17,7 @@
 
 from canonical.config import config
 from lp.archivepublisher.config import getPubConfig
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.registry.interfaces.distribution import IDistributionSet
 from lp.registry.interfaces.person import IPersonSet
 from lp.services.log.logger import BufferLogger
@@ -274,13 +275,15 @@
             self.runPublishDistro, ['--primary-debug'])
 
         # The DEBUG repository path was not created.
+        ubuntutest = getUtility(IDistributionSet)['ubuntutest']
+        root_dir = getUtility(
+            IPublisherConfigSet).getByDistribution(ubuntutest).root_dir
         repo_path = os.path.join(
-            config.archivepublisher.root, 'ubuntutest-debug')
+            root_dir, 'ubuntutest-debug')
         self.assertNotExists(repo_path)
 
         # We will create the DEBUG archive for ubuntutest, so it can
         # be published.
-        ubuntutest = getUtility(IDistributionSet)['ubuntutest']
         debug_archive = getUtility(IArchiveSet).new(
             purpose=ArchivePurpose.DEBUG, owner=ubuntutest.owner,
             distribution=ubuntutest)
@@ -319,8 +322,10 @@
         copy_archive_name = 'test-copy-publish'
 
         # The COPY repository path is not created yet.
+        root_dir = getUtility(
+            IPublisherConfigSet).getByDistribution(ubuntutest).root_dir
         repo_path = os.path.join(
-            config.archivepublisher.root,
+            root_dir,
             ubuntutest.name + '-' + copy_archive_name,
             ubuntutest.name)
         self.assertNotExists(repo_path)

=== modified file 'lib/lp/soyuz/stories/distroseries/add-architecture.txt'
--- lib/lp/soyuz/stories/distroseries/add-architecture.txt	2009-11-09 17:08:21 +0000
+++ lib/lp/soyuz/stories/distroseries/add-architecture.txt	2011-03-17 21:14:13 +0000
@@ -27,6 +27,16 @@
     >>> print admin_browser.title
     ia64 : Hoary (5.04) : Ubuntu
 
+Architecture tag is restricted to the usual Launchpad name format.
+
+    >>> admin_browser.open('http://launchpad.dev/ubuntu/hoary')
+    >>> admin_browser.getLink('Add architecture').click()
+    >>> admin_browser.getControl('Architecture Tag').value = 'foo bar'
+    >>> admin_browser.getControl('Continue').click()
+    >>> print "\n".join(get_feedback_messages(admin_browser.contents))
+    There is 1 error.
+    Invalid name 'foo bar'. ...
+
 Other users won't see the link nor the page where a new port can be
 registered.
 

=== modified file 'lib/lp/soyuz/tests/test_packageupload.py'
--- lib/lp/soyuz/tests/test_packageupload.py	2010-12-20 06:46:09 +0000
+++ lib/lp/soyuz/tests/test_packageupload.py	2011-03-17 21:14:13 +0000
@@ -12,6 +12,7 @@
 from canonical.config import config
 from canonical.testing.layers import LaunchpadZopelessLayer
 from lp.archiveuploader.tests import datadir
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.buildmaster.enums import BuildStatus
 from lp.registry.interfaces.distribution import IDistributionSet
 from lp.registry.interfaces.pocket import PackagePublishingPocket
@@ -158,22 +159,26 @@
             self.assertEqual(
                 restricted, pub_file.libraryfilealias.restricted)
 
-    def removeRepository(self):
+    def removeRepository(self, distro):
         """Remove the testing repository root if it exists."""
-        if os.path.exists(config.archivepublisher.root):
-            shutil.rmtree(config.archivepublisher.root)
+        root = getUtility(
+            IPublisherConfigSet).getByDistribution(distro).root_dir
+        if os.path.exists(root):
+            shutil.rmtree(root)
 
     def test_realiseUpload_for_delayed_copies(self):
         # Delayed-copies result in published records that were overridden
         # and has their files privacy adjusted according test destination
         # context.
 
+        # Create the default delayed-copy context.
+        delayed_copy = self.createDelayedCopy()
+
         # Add a cleanup for removing the repository where the custom upload
         # was published.
-        self.addCleanup(self.removeRepository)
-
-        # Create the default delayed-copy context.
-        delayed_copy = self.createDelayedCopy()
+        self.addCleanup(
+            self.removeRepository,
+            self.test_publisher.breezy_autotest.distribution)
 
         # Delayed-copies targeted to unreleased pockets cannot be accepted.
         self.assertRaisesWithContent(
@@ -216,6 +221,7 @@
         # production.  The user's environment might have a different umask, so
         # just force it to what the test expects.
         old_umask = os.umask(022)
+
         try:
             pub_records = delayed_copy.realiseUpload(logger=logger)
         finally:
@@ -271,8 +277,10 @@
         self.assertFalse(package_diff.diff_content.restricted)
 
         # The custom file was also published.
+        root_dir = getUtility(IPublisherConfigSet).getByDistribution(
+            self.test_publisher.breezy_autotest.distribution).root_dir
         custom_path = os.path.join(
-            config.archivepublisher.root,
+            root_dir,
             'ubuntutest/dists/breezy-autotest-security',
             'main/dist-upgrader-all')
         self.assertEquals(

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2011-03-16 14:33:28 +0000
+++ lib/lp/testing/factory.py	2011-03-17 21:14:13 +0000
@@ -101,6 +101,7 @@
     )
 from canonical.launchpad.webapp.sorting import sorted_version_numbers
 from lp.app.enums import ServiceUsage
+from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.archiveuploader.dscfile import DSCFile
 from lp.archiveuploader.uploadpolicy import BuildDaemonUploadPolicy
 from lp.blueprints.enums import (
@@ -2216,8 +2217,10 @@
         return library_file_alias
 
     def makeDistribution(self, name=None, displayname=None, owner=None,
-                         members=None, title=None, aliases=None,
-                         bug_supervisor=None):
+                         registrant=None, members=None, title=None,
+                         aliases=None, bug_supervisor=None,
+                         publish_root_dir=None, publish_base_url=None,
+                         publish_copy_base_url=None, no_pubconf=False):
         """Make a new distribution."""
         if name is None:
             name = self.getUniqueString(prefix="distribution")
@@ -2228,18 +2231,24 @@
         description = self.getUniqueString()
         summary = self.getUniqueString()
         domainname = self.getUniqueString()
+        if registrant is None:
+            registrant = self.makePerson()
         if owner is None:
             owner = self.makePerson()
         if members is None:
             members = self.makeTeam(owner)
         distro = getUtility(IDistributionSet).new(
             name, displayname, title, description, summary, domainname,
-            members, owner)
+            members, owner, registrant)
         if aliases is not None:
             removeSecurityProxy(distro).setAliases(aliases)
         if bug_supervisor is not None:
             naked_distro = removeSecurityProxy(distro)
             naked_distro.bug_supervisor = bug_supervisor
+        if not no_pubconf:
+            self.makePublisherConfig(
+                distro, publish_root_dir, publish_base_url,
+                publish_copy_base_url)
         return distro
 
     def makeDistroRelease(self, distribution=None, version=None,
@@ -3898,6 +3907,20 @@
             description = self.getUniqueString()
         return getUtility(ICveSet).new(sequence, description, cvestate)
 
+    def makePublisherConfig(self, distribution=None, root_dir=None,
+                            base_url=None, copy_base_url=None):
+        """Create a new `PublisherConfig` record."""
+        if distribution is None:
+            distribution = self.makeDistribution()
+        if root_dir is None:
+            root_dir = self.getUniqueUnicode()
+        if base_url is None:
+            base_url = self.getUniqueUnicode()
+        if copy_base_url is None:
+            copy_base_url = self.getUniqueUnicode()
+        return getUtility(IPublisherConfigSet).new(
+            distribution, root_dir, base_url, copy_base_url)
+
 
 # Some factory methods return simple Python types. We don't add
 # security wrappers for them, as well as for objects created by

=== modified file 'lib/lp/testing/tests/test_standard_test_template.py'
--- lib/lp/testing/tests/test_standard_test_template.py	2011-02-02 13:19:02 +0000
+++ lib/lp/testing/tests/test_standard_test_template.py	2011-03-17 21:14:13 +0000
@@ -6,15 +6,16 @@
 __metaclass__ = type
 
 from canonical.testing.layers import DatabaseFunctionalLayer
-from lp.testing import TestCase # or TestCaseWithFactory
+# or TestCaseWithFactory
+from lp.testing import TestCase
 
 
 class TestSomething(TestCase):
     # XXX: Sample test class.  Replace with your own test class(es).
 
-    # XXX: Optional layer--see lib/canonical/testing/layers.py
-    # Get the simplest layer that your test will work on, or if you
-    # don't even use the database, don't set it at all.
+    # XXX: layer--see lib/canonical/testing/layers.py
+    # Get the simplest layer that your test will work on. For unit tests
+    # requiring no resources, this is BaseLayer.
     layer = DatabaseFunctionalLayer
 
     # XXX: Sample test.  Replace with your own test methods.