← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~michael.nelson/launchpad/649599-ajax-comment-on-dsdiff into lp:launchpad/devel

 

Michael Nelson has proposed merging lp:~michael.nelson/launchpad/649599-ajax-comment-on-dsdiff into lp:launchpad/devel.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)



-- 
https://code.launchpad.net/~michael.nelson/launchpad/649599-ajax-comment-on-dsdiff/+merge/36965
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~michael.nelson/launchpad/649599-ajax-comment-on-dsdiff into lp:launchpad/devel.
=== modified file 'database/replication/Makefile'
--- database/replication/Makefile	2010-07-26 08:12:20 +0000
+++ database/replication/Makefile	2010-09-29 09:44:52 +0000
@@ -14,8 +14,9 @@
 # To test the staging rebuild script:
 #
 #  $ cd database/replication
-#  $ pg_dump --format=c launchpad_dev > launchpad.dump
-#  $ make stagingsetup STAGING_CONFIG=dev-staging STAGING_DUMP=launchpad.dump
+#  $ pg_dump --format=c launchpad_dev | bzip2 -c > launchpad.dump.bz2
+#  $ make stagingsetup \
+#        STAGING_CONFIG=dev-staging STAGING_DUMP=launchpad.dump.bz2
 #  $ make stagingswitch STAGING_CONFIG=dev-staging
 #
 # To restore a dogfood database:
@@ -46,7 +47,12 @@
 
 CREATEDB_83=createdb --encoding=UTF8
 CREATEDB_84=createdb --encoding=UTF8 --locale=C --template=template0
-CREATEDB=${CREATEDB_83}
+CREATEDB=${CREATEDB_84}
+
+# Set this to --exit-on-error once our dumps are coming from a PG 8.4
+# source. Currently, the PG 8.3 dumps generate some spurious errors
+# when being restored into a PG 8.4 database.
+EXIT_ON_ERROR=
 
 # Turn off output silencing so we can see details of staging deployments.
 # Without the timestamps, we are unable to estimate production deployment
@@ -102,8 +108,9 @@
 	# Restore the database. We need to restore permissions, despite
 	# later running security.py, to pull in permissions granted on
 	# production to users not maintained by security.py.
-	bunzip2 --stdout ${STAGING_DUMP} | \
-	    pg_restore --dbname=lpmain_staging_new --no-owner --exit-on-error
+	# Stop ignoring error code after dumps come from an 8.4 system.
+	-bunzip2 --stdout ${STAGING_DUMP} | \
+	    pg_restore --dbname=lpmain_staging_new --no-owner ${EXIT_ON_ERROR}
 	# Uninstall Slony-I if it is installed - a pg_dump of a DB with
 	# Slony-I installed isn't usable without this step.
 	LPCONFIG=${NEW_STAGING_CONFIG} ./repair-restored-db.py
@@ -136,8 +143,9 @@
 
 dogfood:
 	${CREATEDB} ${DOGFOOD_DBNAME}
-	pg_restore --dbname=${DOGFOOD_DBNAME} --no-acl --no-owner \
-	    --exit-on-error ${DOGFOOD_DUMP}
+	# Stop ignoring error code after are dumps come from an 8.4 system.
+	-pg_restore --dbname=${DOGFOOD_DBNAME} --no-acl --no-owner \
+	    ${EXIT_ON_ERROR} ${DOGFOOD_DUMP}
 	./repair-restored-db.py -d ${DOGFOOD_DBNAME}
 	../schema/upgrade.py -d ${DOGFOOD_DBNAME}
 	../schema/fti.py -d ${DOGFOOD_DBNAME}

=== modified file 'database/replication/slon_ctl.py'
--- database/replication/slon_ctl.py	2010-05-19 18:07:56 +0000
+++ database/replication/slon_ctl.py	2010-09-29 09:44:52 +0000
@@ -88,9 +88,11 @@
 
 
 def get_logfile(nickname):
+    logdir = config.database.replication_logdir
+    if not os.path.isabs(logdir):
+        logdir = os.path.normpath(os.path.join(config.root, logdir))
     return os.path.join(
-        config.root, 'database', 'replication',
-        'lpslon_%s_%s.log' % (nickname, config.instance_name))
+        logdir, 'lpslon_%s_%s.log' % (nickname, config.instance_name))
 
 
 def start(log, nodes, lag=None):

=== modified file 'database/sampledata/current-dev.sql'
--- database/sampledata/current-dev.sql	2010-09-23 19:10:34 +0000
+++ database/sampledata/current-dev.sql	2010-09-29 09:44:52 +0000
@@ -792,24 +792,57 @@
 
 
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+<<<<<<< TREE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+=======
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+>>>>>>> MERGE-SOURCE
 SET SESSION AUTHORIZATION DEFAULT;
 
 ALTER TABLE account DISABLE TRIGGER ALL;
@@ -1837,14 +1870,14 @@
 
 ALTER TABLE distroarchseries DISABLE TRIGGER ALL;
 
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (1, 1, 1, 'i386', 1, true, 5, '2006-10-16 18:31:43.454475', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (6, 3, 1, 'i386', 1, true, 1, '2006-10-16 18:31:43.456532', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (7, 6, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457028', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (8, 10, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457484', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (9, 13, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457938', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (10, 13, 3, 'amd64', 1, true, 0, '2006-10-16 18:31:43.458434', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (11, 3, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.458892', false);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (12, 1, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.459349', false);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (1, 1, 1, 'i386', 1, true, 5, '2006-10-16 18:31:43.454475', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (6, 3, 1, 'i386', 1, true, 1, '2006-10-16 18:31:43.456532', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (7, 6, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457028', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (8, 10, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457484', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (9, 13, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457938', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (10, 13, 3, 'amd64', 1, true, 0, '2006-10-16 18:31:43.458434', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (11, 3, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.458892', false, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (12, 1, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.459349', false, true);
 
 
 ALTER TABLE distroarchseries ENABLE TRIGGER ALL;
@@ -3092,25 +3125,25 @@
 
 ALTER TABLE branchrevision DISABLE TRIGGER ALL;
 
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (1, 1, 10, 1);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (2, 1, 11, 2);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (3, 1, 12, 3);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (4, 1, 20, 4);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (5, 2, 20, 5);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (6, 3, 20, 6);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (7, 4, 20, 7);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (8, 5, 20, 8);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (9, 6, 20, 9);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (10, 1, 21, 4);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (11, 2, 21, 5);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (12, 3, 21, 10);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (13, 4, 21, 11);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (14, 5, 21, 8);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (15, 6, 21, 9);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (16, NULL, 20, 10);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (17, NULL, 20, 11);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (18, NULL, 21, 6);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (19, NULL, 21, 7);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 10, 1);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 11, 2);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 12, 3);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 20, 4);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 21, 4);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (2, 20, 5);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (2, 21, 5);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (3, 20, 6);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (3, 21, 10);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (4, 20, 7);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (4, 21, 11);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (5, 20, 8);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (5, 21, 8);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (6, 20, 9);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (6, 21, 9);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 20, 10);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 20, 11);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 21, 6);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 21, 7);
 
 
 ALTER TABLE branchrevision ENABLE TRIGGER ALL;
@@ -3333,42 +3366,42 @@
 
 ALTER TABLE bugmessage DISABLE TRIGGER ALL;
 
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (1, 2, 1, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (2, 1, 3, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (3, 1, 4, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (4, 2, 5, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (5, 2, 6, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (6, 4, 7, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (7, 5, 8, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (8, 6, 9, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (9, 3, 10, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (10, 7, 11, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (11, 8, 14, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (12, 9, 15, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (13, 10, 17, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (14, 10, 16, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (15, 11, 24, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (16, 11, 25, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (17, 11, 26, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (18, 11, 27, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (19, 11, 28, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (20, 11, 29, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (21, 11, 30, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (22, 12, 31, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (23, 12, 33, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (24, 12, 34, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (25, 12, 35, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (26, 12, 36, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (27, 13, 37, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (28, 13, 38, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (29, 14, 39, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (30, 15, 40, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@xxxxxx>', true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (33, 15, 46, 11, '<428A44E9.6090802@xxxxxx>', true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@xxxxxxxxx>', true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@xxxxxx>', true);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (1, 2, 1, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (2, 1, 3, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (3, 1, 4, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (4, 2, 5, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (5, 2, 6, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (6, 4, 7, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (7, 5, 8, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (8, 6, 9, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (9, 3, 10, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (10, 7, 11, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (11, 8, 14, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (12, 9, 15, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (13, 10, 17, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (14, 10, 16, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (15, 11, 24, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (16, 11, 25, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (17, 11, 26, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (18, 11, 27, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (19, 11, 28, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (20, 11, 29, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (21, 11, 30, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (22, 12, 31, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (23, 12, 33, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (24, 12, 34, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (25, 12, 35, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (26, 12, 36, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (27, 13, 37, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (28, 13, 38, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (29, 14, 39, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (30, 15, 40, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@xxxxxx>', true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (33, 15, 46, 11, '<428A44E9.6090802@xxxxxx>', true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@xxxxxxxxx>', true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@xxxxxx>', true, NULL);
 
 
 ALTER TABLE bugmessage ENABLE TRIGGER ALL;
@@ -3792,6 +3825,27 @@
 ALTER TABLE bugtrackeralias ENABLE TRIGGER ALL;
 
 
+ALTER TABLE bugtrackercomponentgroup DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE bugtrackercomponentgroup ENABLE TRIGGER ALL;
+
+
+ALTER TABLE distributionsourcepackage DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE distributionsourcepackage ENABLE TRIGGER ALL;
+
+
+ALTER TABLE bugtrackercomponent DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE bugtrackercomponent ENABLE TRIGGER ALL;
+
+
 ALTER TABLE bugtrackerperson DISABLE TRIGGER ALL;
 
 
@@ -3993,6 +4047,13 @@
 ALTER TABLE distributionbounty ENABLE TRIGGER ALL;
 
 
+ALTER TABLE distributionjob DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE distributionjob ENABLE TRIGGER ALL;
+
+
 ALTER TABLE distributionmirror DISABLE TRIGGER ALL;
 
 INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer, country_dns_mirror) VALUES (1, 1, 'archive-mirror', 'http://localhost:11375/valid-mirror/', NULL, NULL, NULL, NULL, 1, 10, 75, 1, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL, false);
@@ -4010,13 +4071,6 @@
 ALTER TABLE distributionmirror ENABLE TRIGGER ALL;
 
 
-ALTER TABLE distributionsourcepackage DISABLE TRIGGER ALL;
-
-
-
-ALTER TABLE distributionsourcepackage ENABLE TRIGGER ALL;
-
-
 ALTER TABLE distributionsourcepackagecache DISABLE TRIGGER ALL;
 
 INSERT INTO distributionsourcepackagecache (id, distribution, sourcepackagename, name, binpkgnames, binpkgsummaries, binpkgdescriptions, fti, changelog, archive) VALUES (1, 3, 19, 'alsa-utils', '', '', '', NULL, NULL, 1);
@@ -4567,6 +4621,13 @@
 ALTER TABLE hwtestanswerdevice ENABLE TRIGGER ALL;
 
 
+ALTER TABLE incrementaldiff DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE incrementaldiff ENABLE TRIGGER ALL;
+
+
 ALTER TABLE ircid DISABLE TRIGGER ALL;
 
 INSERT INTO ircid (id, person, network, nickname) VALUES (1, 1, 'irc.freenode.net', 'mark');
@@ -11141,6 +11202,13 @@
 ALTER TABLE translationtemplateitem ENABLE TRIGGER ALL;
 
 
+ALTER TABLE translationtemplatesbuild DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE translationtemplatesbuild ENABLE TRIGGER ALL;
+
+
 ALTER TABLE translator DISABLE TRIGGER ALL;
 
 INSERT INTO translator (id, translationgroup, language, translator, datecreated, style_guide_url) VALUES (1, 1, 387, 53, '2005-07-13 13:14:19.748396', NULL);

=== modified file 'database/sampledata/current.sql'
--- database/sampledata/current.sql	2010-09-04 20:38:35 +0000
+++ database/sampledata/current.sql	2010-09-29 09:44:52 +0000
@@ -792,6 +792,36 @@
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 SET SESSION AUTHORIZATION DEFAULT;
 
 ALTER TABLE account DISABLE TRIGGER ALL;
@@ -1819,14 +1849,14 @@
 
 ALTER TABLE distroarchseries DISABLE TRIGGER ALL;
 
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (1, 1, 1, 'i386', 1, true, 5, '2006-10-16 18:31:43.454475', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (6, 3, 1, 'i386', 1, true, 1, '2006-10-16 18:31:43.456532', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (7, 6, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457028', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (8, 10, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457484', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (9, 13, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457938', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (10, 13, 3, 'amd64', 1, true, 0, '2006-10-16 18:31:43.458434', true);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (11, 3, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.458892', false);
-INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized) VALUES (12, 1, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.459349', false);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (1, 1, 1, 'i386', 1, true, 5, '2006-10-16 18:31:43.454475', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (6, 3, 1, 'i386', 1, true, 1, '2006-10-16 18:31:43.456532', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (7, 6, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457028', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (8, 10, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457484', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (9, 13, 1, 'i386', 1, true, 0, '2006-10-16 18:31:43.457938', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (10, 13, 3, 'amd64', 1, true, 0, '2006-10-16 18:31:43.458434', true, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (11, 3, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.458892', false, true);
+INSERT INTO distroarchseries (id, distroseries, processorfamily, architecturetag, owner, official, package_count, date_created, supports_virtualized, enabled) VALUES (12, 1, 4, 'hppa', 1, false, 0, '2006-10-16 18:31:43.459349', false, true);
 
 
 ALTER TABLE distroarchseries ENABLE TRIGGER ALL;
@@ -2238,8 +2268,8 @@
 
 ALTER TABLE builder DISABLE TRIGGER ALL;
 
-INSERT INTO builder (id, processor, name, title, description, owner, speedindex, builderok, failnotes, virtualized, url, manual, date_created, vm_host, active) VALUES (1, 1, 'bob', 'Bob The Builder', 'The default build-slave', 61, NULL, true, NULL, false, 'http://localhost:8221/', false, '2006-10-16 18:31:43.226724', NULL, true);
-INSERT INTO builder (id, processor, name, title, description, owner, speedindex, builderok, failnotes, virtualized, url, manual, date_created, vm_host, active) VALUES (2, 1, 'frog', 'The frog builder', 'The untrusted build-slave', 61, NULL, false, NULL, true, 'http://localhost:9221/', false, '2006-10-31 18:31:43.226724', 'localhost-host.ppa', true);
+INSERT INTO builder (id, processor, name, title, description, owner, speedindex, builderok, failnotes, virtualized, url, manual, date_created, vm_host, active, failure_count) VALUES (1, 1, 'bob', 'Bob The Builder', 'The default build-slave', 61, NULL, true, NULL, false, 'http://localhost:8221/', false, '2006-10-16 18:31:43.226724', NULL, true, 0);
+INSERT INTO builder (id, processor, name, title, description, owner, speedindex, builderok, failnotes, virtualized, url, manual, date_created, vm_host, active, failure_count) VALUES (2, 1, 'frog', 'The frog builder', 'The untrusted build-slave', 61, NULL, false, NULL, true, 'http://localhost:9221/', false, '2006-10-31 18:31:43.226724', 'localhost-host.ppa', true, 0);
 
 
 ALTER TABLE builder ENABLE TRIGGER ALL;
@@ -2247,30 +2277,30 @@
 
 ALTER TABLE buildfarmjob DISABLE TRIGGER ALL;
 
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (2, 1, false, '2004-09-27 11:57:13', '2004-09-27 11:55:13', '2004-09-27 11:57:14', NULL, 1, 1, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (6, 1, false, '2006-12-01 00:00:00', '2006-12-01 00:00:00', '2006-12-01 00:00:01', NULL, 1, 2, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (7, 1, false, '2005-03-24 00:00:00', '2005-03-24 23:58:43', '2005-03-25 00:00:03', NULL, 1, 1, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (8, 1, false, '2005-09-30 00:00:00', NULL, NULL, NULL, NULL, 6, NULL, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (9, 1, false, '2005-10-01 00:00:00', '2005-10-01 23:56:41', '2005-10-02 00:00:01', NULL, 1, 2, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (10, 1, false, '2006-01-27 00:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (11, 1, false, '2006-02-14 00:00:00', NULL, NULL, NULL, NULL, 0, NULL, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (12, 1, false, '2006-02-28 00:00:00', '2006-02-27 23:53:59', '2006-02-28 00:00:01', NULL, 1, 3, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (13, 1, false, '2006-03-21 00:00:00', '2006-03-21 00:58:33', '2006-03-21 01:00:03', NULL, 1, 5, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (14, 1, false, '2006-03-22 00:00:00', '2006-03-21 00:58:32', '2006-03-21 01:00:02', NULL, 1, 5, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (15, 1, false, '2006-03-22 00:00:01', '2006-03-21 00:58:30', '2006-03-21 01:00:00', NULL, 1, 5, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (16, 1, false, '2005-03-24 00:00:01', '2005-03-24 23:58:42', '2005-03-25 00:00:02', NULL, 1, 1, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (18, 1, false, '2004-09-27 11:57:14', '2004-09-27 11:55:12', '2004-09-27 11:57:13', NULL, 1, 1, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (19, 1, false, '2005-03-24 00:00:02', '2005-03-24 23:58:41', '2005-03-25 00:00:01', NULL, 1, 1, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (21, 1, false, '2006-12-01 00:00:01', NULL, NULL, NULL, NULL, 2, NULL, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (22, 1, false, '2007-04-20 00:00:00', '2007-04-19 23:58:41', '2007-04-20 00:00:01', NULL, 1, 7, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (23, 1, false, '2006-04-11 12:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (24, 1, true, '2007-05-30 00:00:00', '2007-05-29 23:58:41', '2007-05-30 00:00:01', NULL, 1, 2, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (25, 1, true, '2007-07-08 12:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (26, 1, true, '2007-07-08 00:00:00', '2007-07-07 23:58:41', '2007-07-08 00:00:01', NULL, 1, 2, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (27, 1, true, '2007-07-24 00:00:00', '2007-07-23 23:58:41', '2007-07-24 00:00:01', NULL, 1, 1, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (28, 3, true, '2007-08-10 00:00:00', '2007-08-10 00:00:00', '2007-08-10 00:00:13', NULL, 1, 1, 1, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (29, 1, false, '2007-08-09 21:54:18.553132', '2007-08-09 23:49:59', '2007-08-09 23:59:59', NULL, NULL, 1, NULL, 1);
-INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type) VALUES (30, 3, false, '2007-08-10 00:00:01', '2007-08-10 00:00:01', '2007-08-10 00:00:14', NULL, 1, 1, 1, 1);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (2, 1, false, '2004-09-27 11:57:13', '2004-09-27 11:55:13', '2004-09-27 11:57:14', NULL, 1, 1, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (6, 1, false, '2006-12-01 00:00:00', '2006-12-01 00:00:00', '2006-12-01 00:00:01', NULL, 1, 2, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (7, 1, false, '2005-03-24 00:00:00', '2005-03-24 23:58:43', '2005-03-25 00:00:03', NULL, 1, 1, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (8, 1, false, '2005-09-30 00:00:00', NULL, NULL, NULL, NULL, 6, NULL, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (9, 1, false, '2005-10-01 00:00:00', '2005-10-01 23:56:41', '2005-10-02 00:00:01', NULL, 1, 2, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (10, 1, false, '2006-01-27 00:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (11, 1, false, '2006-02-14 00:00:00', NULL, NULL, NULL, NULL, 0, NULL, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (12, 1, false, '2006-02-28 00:00:00', '2006-02-27 23:53:59', '2006-02-28 00:00:01', NULL, 1, 3, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (13, 1, false, '2006-03-21 00:00:00', '2006-03-21 00:58:33', '2006-03-21 01:00:03', NULL, 1, 5, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (14, 1, false, '2006-03-22 00:00:00', '2006-03-21 00:58:32', '2006-03-21 01:00:02', NULL, 1, 5, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (15, 1, false, '2006-03-22 00:00:01', '2006-03-21 00:58:30', '2006-03-21 01:00:00', NULL, 1, 5, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (16, 1, false, '2005-03-24 00:00:01', '2005-03-24 23:58:42', '2005-03-25 00:00:02', NULL, 1, 1, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (18, 1, false, '2004-09-27 11:57:14', '2004-09-27 11:55:12', '2004-09-27 11:57:13', NULL, 1, 1, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (19, 1, false, '2005-03-24 00:00:02', '2005-03-24 23:58:41', '2005-03-25 00:00:01', NULL, 1, 1, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (21, 1, false, '2006-12-01 00:00:01', NULL, NULL, NULL, NULL, 2, NULL, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (22, 1, false, '2007-04-20 00:00:00', '2007-04-19 23:58:41', '2007-04-20 00:00:01', NULL, 1, 7, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (23, 1, false, '2006-04-11 12:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (24, 1, true, '2007-05-30 00:00:00', '2007-05-29 23:58:41', '2007-05-30 00:00:01', NULL, 1, 2, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (25, 1, true, '2007-07-08 12:00:00', NULL, NULL, NULL, NULL, 1, NULL, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (26, 1, true, '2007-07-08 00:00:00', '2007-07-07 23:58:41', '2007-07-08 00:00:01', NULL, 1, 2, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (27, 1, true, '2007-07-24 00:00:00', '2007-07-23 23:58:41', '2007-07-24 00:00:01', NULL, 1, 1, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (28, 3, true, '2007-08-10 00:00:00', '2007-08-10 00:00:00', '2007-08-10 00:00:13', NULL, 1, 1, 1, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (29, 1, false, '2007-08-09 21:54:18.553132', '2007-08-09 23:49:59', '2007-08-09 23:59:59', NULL, NULL, 1, NULL, 1, 0);
+INSERT INTO buildfarmjob (id, processor, virtualized, date_created, date_started, date_finished, date_first_dispatched, builder, status, log, job_type, failure_count) VALUES (30, 3, false, '2007-08-10 00:00:01', '2007-08-10 00:00:01', '2007-08-10 00:00:14', NULL, 1, 1, 1, 1, 0);
 
 
 ALTER TABLE buildfarmjob ENABLE TRIGGER ALL;
@@ -2365,29 +2395,29 @@
 
 ALTER TABLE sourcepackagerelease DISABLE TRIGGER ALL;
 
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (14, 1, '0.9', '2004-09-27 11:57:13', 1, NULL, 1, 'Mozilla dummy Changelog......', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1), pmount', 'bacula-common (= 1.34.6-2), bacula-director-common (= 1.34.6-2), postgresql-client (>= 7.4), pmount', 'any', NULL, 1, 1, 1, 1, 1, 'Mark Shuttleworth <mark@xxxxxxxxxxxxx>', '3.6.2', '1.0', 'mozilla-firefox', 1, NULL, 'gcc-4.0, pmount', 'gcc-4.0-base, pmount', NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (15, 1, '1.0', '2004-09-27 11:57:13', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 2, 1, 9, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (16, 1, '1.0-1', '2005-03-10 16:30:00', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 3, 1, 10, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (17, 1, '0.99.6-1', '2005-03-14 18:00:00', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 2, 1, 10, 1, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (20, 1, '0.1-1', '2005-03-24 20:59:31.439579', 1, NULL, 1, 'pmount (0.1-1) hoary; urgency=low
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (14, 1, '0.9', '2004-09-27 11:57:13', 1, NULL, 1, 'Mozilla dummy Changelog......', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1), pmount', 'bacula-common (= 1.34.6-2), bacula-director-common (= 1.34.6-2), postgresql-client (>= 7.4), pmount', 'any', NULL, 1, 1, 1, 1, 1, 'Mark Shuttleworth <mark@xxxxxxxxxxxxx>', '3.6.2', '1.0', 'mozilla-firefox', 1, NULL, 'gcc-4.0, pmount', 'gcc-4.0-base, pmount', NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (15, 1, '1.0', '2004-09-27 11:57:13', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 2, 1, 9, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (16, 1, '1.0-1', '2005-03-10 16:30:00', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 3, 1, 10, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (17, 1, '0.99.6-1', '2005-03-14 18:00:00', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 2, 1, 10, 1, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (20, 1, '0.1-1', '2005-03-24 20:59:31.439579', 1, NULL, 1, 'pmount (0.1-1) hoary; urgency=low
 
  * Fix description (Malone #1)
  * Fix debian (Debian #2000)
  * Fix warty (Warty Ubuntu #1)
 
- -- Sample Person <test@xxxxxxxxxxxxx> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 2, 1, 14, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (21, 1, '0.1-2', '2005-06-24 20:59:31.439579', 1, NULL, 1, 'This is a placeholder changelog for pmount 0.1-2', NULL, NULL, 'powerpc', NULL, 1, 1, 14, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (23, 1, '1.0.8-1ubuntu1', '2005-02-03 08:50:00', 1, NULL, 1, 'alsa-utils (1.0.8-1ubuntu1) warty; urgency=low
-
- * Placeholder
-
-     -- Sample Person <test@xxxxxxxxxxxxx> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 1, 1, 19, 1, 1, 'Mark Shuttleworth <mark@xxxxxxxxxxx>', '3.6.2', '1.0', 'alsa-mixer', 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (24, 1, '1.0.9a-4', '2005-07-01 22:47:00', 1, NULL, 1, 'alsa-utils (1.0.9a-4) warty; urgency=low
-
- * Placeholder
-
-     -- Sample Person <test@xxxxxxxxxxxxx> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'any', NULL, 2, 1, 19, 8, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (25, 1, '1.0.9a-4ubuntu1', '2005-08-01 14:10:00', 1, NULL, 1, 'alsa-utils (1.0.9a-4ubuntu1) hoary; urgency=low
+ -- Sample Person <test@xxxxxxxxxxxxx> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 2, 1, 14, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (21, 1, '0.1-2', '2005-06-24 20:59:31.439579', 1, NULL, 1, 'This is a placeholder changelog for pmount 0.1-2', NULL, NULL, 'powerpc', NULL, 1, 1, 14, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (23, 1, '1.0.8-1ubuntu1', '2005-02-03 08:50:00', 1, NULL, 1, 'alsa-utils (1.0.8-1ubuntu1) warty; urgency=low
+
+ * Placeholder
+
+     -- Sample Person <test@xxxxxxxxxxxxx> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 1, 1, 19, 1, 1, 'Mark Shuttleworth <mark@xxxxxxxxxxx>', '3.6.2', '1.0', 'alsa-mixer', 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (24, 1, '1.0.9a-4', '2005-07-01 22:47:00', 1, NULL, 1, 'alsa-utils (1.0.9a-4) warty; urgency=low
+
+ * Placeholder
+
+     -- Sample Person <test@xxxxxxxxxxxxx> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'any', NULL, 2, 1, 19, 8, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (25, 1, '1.0.9a-4ubuntu1', '2005-08-01 14:10:00', 1, NULL, 1, 'alsa-utils (1.0.9a-4ubuntu1) hoary; urgency=low
 
  * Placeholder
  LP: #10
@@ -2396,22 +2426,22 @@
  LP: #7, #8,
    #11
 
-     -- Sample Person <test@xxxxxxxxxxxxx> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 1, 16, 19, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (26, 1, 'cr.g7-37', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 1, 16, 20, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (27, 1, 'b8p', '2006-02-10 11:19:00', 1, NULL, 1, 'libstdc++ (9.9-1) hoary; urgency=high
+     -- Sample Person <test@xxxxxxxxxxxxx> Tue, 7 Feb 2006 12:10:08 +0300', NULL, NULL, 'all', NULL, 1, 16, 19, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (26, 1, 'cr.g7-37', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 1, 16, 20, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (27, 1, 'b8p', '2006-02-10 11:19:00', 1, NULL, 1, 'libstdc++ (9.9-1) hoary; urgency=high
 
  * Placeholder
 
- -- Sample Person <test@xxxxxxxxxxxxx> Tue, 10 Feb 2006 10:10:08 +0300', NULL, NULL, 'powerpc i386', NULL, 1, 16, 21, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (28, 1, '2.6.15.3', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 1, 16, 22, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (29, 1, '0.00', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 17, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (30, 1, '1.0', '2006-09-28 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 20, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (31, 1, '1.0', '2006-09-28 18:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 20, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (32, 1, '1.0', '2006-12-01 13:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 23, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (33, 1, '1.0', '2006-12-01 13:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 24, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (34, 1, '1.0', '2007-02-15 14:19:01', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 29, 16, 25, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (35, 1, '1.0', '2006-04-11 11:19:01', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 1, 16, 26, 1, 1, NULL, NULL, '1.0', NULL, 10, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (36, 243606, '1.0-1', '2007-08-09 21:25:37.832976', 1, NULL, 5, 'commercialpackage (1.0-1) breezy; urgency=low
+ -- Sample Person <test@xxxxxxxxxxxxx> Tue, 10 Feb 2006 10:10:08 +0300', NULL, NULL, 'powerpc i386', NULL, 1, 16, 21, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (28, 1, '2.6.15.3', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 1, 16, 22, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (29, 1, '0.00', '2005-12-22 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 17, 3, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (30, 1, '1.0', '2006-09-28 18:19:00', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 20, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (31, 1, '1.0', '2006-09-28 18:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 20, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (32, 1, '1.0', '2006-12-01 13:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 23, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (33, 1, '1.0', '2006-12-01 13:19:01', 1, NULL, 1, NULL, NULL, NULL, 'all', NULL, 1, 16, 24, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (34, 1, '1.0', '2007-02-15 14:19:01', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 29, 16, 25, 10, 1, NULL, NULL, '1.0', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (35, 1, '1.0', '2006-04-11 11:19:01', 1, NULL, 1, NULL, NULL, NULL, 'any', NULL, 1, 16, 26, 1, 1, NULL, NULL, '1.0', NULL, 10, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (36, 243606, '1.0-1', '2007-08-09 21:25:37.832976', 1, NULL, 5, 'commercialpackage (1.0-1) breezy; urgency=low
 
   * Initial version
       Address for testing linkification: Foo Bar <foo.bar@xxxxxxxxxxxxx>
@@ -2435,8 +2465,8 @@
 iD8DBQFGtzTjWhGlTF8G/HcRAtFsAJ4hHyKhOnsUOQDI+SAk000DmFAnUgCcC84J
 3F4bEPeRcnUjCFI/hjR0kxg=
 =Tjln
-', 7, 243606, 27, 10, 1, 'Julian Edwards <launchpad@xxxxxxxxxxxxxxxxxx>', '3.6.2', '1.0', 'commercialpackage', 12, NULL, NULL, NULL, NULL, NULL, NULL);
-INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields) VALUES (37, 1, '1.0', '2006-04-11 11:19:01', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 1, 16, 26, 1, 1, NULL, NULL, '1.0', NULL, 11, NULL, NULL, NULL, NULL, NULL, NULL);
+', 7, 243606, 27, 10, 1, 'Julian Edwards <launchpad@xxxxxxxxxxxxxxxxxx>', '3.6.2', '1.0', 'commercialpackage', 12, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO sourcepackagerelease (id, creator, version, dateuploaded, urgency, dscsigningkey, component, changelog_entry, builddepends, builddependsindep, architecturehintlist, dsc, section, maintainer, sourcepackagename, upload_distroseries, format, dsc_maintainer_rfc822, dsc_standards_version, dsc_format, dsc_binaries, upload_archive, copyright, build_conflicts, build_conflicts_indep, sourcepackage_recipe_build, changelog, user_defined_fields, homepage) VALUES (37, 1, '1.0', '2006-04-11 11:19:01', 1, NULL, 1, NULL, NULL, NULL, 'i386', NULL, 1, 16, 26, 1, 1, NULL, NULL, '1.0', NULL, 11, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
 
 
 ALTER TABLE sourcepackagerelease ENABLE TRIGGER ALL;
@@ -2490,21 +2520,21 @@
 
 ALTER TABLE binarypackagerelease DISABLE TRIGGER ALL;
 
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (6, 6, '1.0', 'foobar is bad', 'foobar should be removed', 6, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2006-12-01 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (12, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 2, 1, 1, 1, 10, NULL, 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'firefox-gnome-support (= 1.0.7-0ubuntu20), latex-xft-fonts, xprint', 'firefox, mozilla-web-browser', 'gnome-mozilla-browser', 'mozilla-firefox', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', 'pmount, foo', 'pmount, bar', 'pmount, baz', NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (15, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 7, 1, 1, 1, 40, NULL, 'at (>= 3.14156), linux-2.6.12, tramp-package', NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (16, 14, '2.6.12.20', 'the kernel of boom', 'this kernel is like the crystal method: a temple of boom', 14, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (17, 15, '3.14156', 'at the mountains of madness', 'lovecraft long before enunciated that the mountains were not safe, but you did not believe him', 15, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (18, 13, '2:1.9-1', 'pmount shortdesc', 'pmount description', 16, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (19, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 18, 1, 1, 1, 10, NULL, 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'firefox-gnome-support (= 1.0.7-0ubuntu20), latex-xft-fonts, xprint', 'firefox, mozilla-web-browser', 'gnome-mozilla-browser', 'mozilla-firefox', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (20, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 19, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (21, 16, '1.0', 'cdrkit is nice', 'cdrkit should be kept', 21, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2006-12-01 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (22, 8, '1.0', 'ff from iceweasel', 'iceweasel huh ?', 23, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2006-04-11 12:50:10.878712', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (23, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 27, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2007-07-24 17:50:10.878712', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (24, 8, '1.0', 'ff from iceweasel', 'iceweasel huh ?', 28, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2006-08-10 12:50:10.878712', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (25, 17, '1.0-1', 'Stuff for testing', ' This package is simply used for testing soyuz', 29, 1, 5, 7, 20, '', '', '', '', '', '', '', false, 8, true, NULL, '2007-08-09 21:54:18.456616', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (26, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 30, 1, 1, 1, 10, '', '', '', '', '', '', '', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL);
-INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields) VALUES (27, 18, '0.9', 'Mozilla Firefox Data', 'Mozilla Firefox Data is .....', 2, 1, 1, 1, 10, '', '', '', '', '', '', '', false, NULL, false, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (6, 6, '1.0', 'foobar is bad', 'foobar should be removed', 6, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2006-12-01 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (12, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 2, 1, 1, 1, 10, NULL, 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'firefox-gnome-support (= 1.0.7-0ubuntu20), latex-xft-fonts, xprint', 'firefox, mozilla-web-browser', 'gnome-mozilla-browser', 'mozilla-firefox', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', 'pmount, foo', 'pmount, bar', 'pmount, baz', NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (15, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 7, 1, 1, 1, 40, NULL, 'at (>= 3.14156), linux-2.6.12, tramp-package', NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (16, 14, '2.6.12.20', 'the kernel of boom', 'this kernel is like the crystal method: a temple of boom', 14, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (17, 15, '3.14156', 'at the mountains of madness', 'lovecraft long before enunciated that the mountains were not safe, but you did not believe him', 15, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (18, 13, '2:1.9-1', 'pmount shortdesc', 'pmount description', 16, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (19, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 18, 1, 1, 1, 10, NULL, 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'gcc-3.4-base, libc6 (>= 2.3.2.ds1-4), gcc-3.4 (>= 3.4.1-4sarge1), gcc-3.4 (<< 3.4.2), libstdc++6-dev (>= 3.4.1-4sarge1)', 'firefox-gnome-support (= 1.0.7-0ubuntu20), latex-xft-fonts, xprint', 'firefox, mozilla-web-browser', 'gnome-mozilla-browser', 'mozilla-firefox', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (20, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 19, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2005-10-19 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (21, 16, '1.0', 'cdrkit is nice', 'cdrkit should be kept', 21, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2006-12-01 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (22, 8, '1.0', 'ff from iceweasel', 'iceweasel huh ?', 23, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2006-04-11 12:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (23, 13, '0.1-1', 'pmount shortdesc', 'pmount description', 27, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, false, NULL, '2007-07-24 17:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (24, 8, '1.0', 'ff from iceweasel', 'iceweasel huh ?', 28, 1, 1, 1, 40, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, true, NULL, '2006-08-10 12:50:10.878712', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (25, 17, '1.0-1', 'Stuff for testing', ' This package is simply used for testing soyuz', 29, 1, 5, 7, 20, '', '', '', '', '', '', '', false, 8, true, NULL, '2007-08-09 21:54:18.456616', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (26, 8, '0.9', 'Mozilla Firefox Web Browser', 'Mozilla Firefox Web Browser is .....', 30, 1, 1, 1, 10, '', '', '', '', '', '', '', false, NULL, true, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL, NULL);
+INSERT INTO binarypackagerelease (id, binarypackagename, version, summary, description, build, binpackageformat, component, section, priority, shlibdeps, depends, recommends, suggests, conflicts, replaces, provides, essential, installedsize, architecturespecific, fti, datecreated, pre_depends, enhances, breaks, debug_package, user_defined_fields, homepage) VALUES (27, 18, '0.9', 'Mozilla Firefox Data', 'Mozilla Firefox Data is .....', 2, 1, 1, 1, 10, '', '', '', '', '', '', '', false, NULL, false, NULL, '2005-10-19 17:50:10.874189', NULL, NULL, NULL, NULL, NULL, NULL);
 
 
 ALTER TABLE binarypackagerelease ENABLE TRIGGER ALL;
@@ -3074,25 +3104,25 @@
 
 ALTER TABLE branchrevision DISABLE TRIGGER ALL;
 
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (1, 1, 10, 1);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (2, 1, 11, 2);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (3, 1, 12, 3);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (4, 1, 20, 4);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (5, 2, 20, 5);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (6, 3, 20, 6);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (7, 4, 20, 7);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (8, 5, 20, 8);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (9, 6, 20, 9);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (10, 1, 21, 4);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (11, 2, 21, 5);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (12, 3, 21, 10);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (13, 4, 21, 11);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (14, 5, 21, 8);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (15, 6, 21, 9);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (16, NULL, 20, 10);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (17, NULL, 20, 11);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (18, NULL, 21, 6);
-INSERT INTO branchrevision (id, sequence, branch, revision) VALUES (19, NULL, 21, 7);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 10, 1);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 11, 2);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 12, 3);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 20, 4);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (1, 21, 4);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (2, 20, 5);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (2, 21, 5);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (3, 20, 6);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (3, 21, 10);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (4, 20, 7);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (4, 21, 11);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (5, 20, 8);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (5, 21, 8);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (6, 20, 9);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (6, 21, 9);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 20, 10);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 20, 11);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 21, 6);
+INSERT INTO branchrevision (sequence, branch, revision) VALUES (NULL, 21, 7);
 
 
 ALTER TABLE branchrevision ENABLE TRIGGER ALL;
@@ -3315,42 +3345,42 @@
 
 ALTER TABLE bugmessage DISABLE TRIGGER ALL;
 
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (1, 2, 1, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (2, 1, 3, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (3, 1, 4, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (4, 2, 5, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (5, 2, 6, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (6, 4, 7, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (7, 5, 8, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (8, 6, 9, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (9, 3, 10, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (10, 7, 11, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (11, 8, 14, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (12, 9, 15, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (13, 10, 17, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (14, 10, 16, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (15, 11, 24, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (16, 11, 25, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (17, 11, 26, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (18, 11, 27, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (19, 11, 28, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (20, 11, 29, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (21, 11, 30, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (22, 12, 31, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (23, 12, 33, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (24, 12, 34, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (25, 12, 35, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (26, 12, 36, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (27, 13, 37, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (28, 13, 38, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (29, 14, 39, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (30, 15, 40, NULL, NULL, true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@xxxxxx>', true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (33, 15, 46, 11, '<428A44E9.6090802@xxxxxx>', true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@xxxxxxxxx>', true);
-INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@xxxxxx>', true);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (1, 2, 1, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (2, 1, 3, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (3, 1, 4, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (4, 2, 5, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (5, 2, 6, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (6, 4, 7, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (7, 5, 8, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (8, 6, 9, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (9, 3, 10, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (10, 7, 11, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (11, 8, 14, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (12, 9, 15, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (13, 10, 17, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (14, 10, 16, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (15, 11, 24, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (16, 11, 25, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (17, 11, 26, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (18, 11, 27, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (19, 11, 28, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (20, 11, 29, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (21, 11, 30, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (22, 12, 31, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (23, 12, 33, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (24, 12, 34, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (25, 12, 35, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (26, 12, 36, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (27, 13, 37, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (28, 13, 38, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (29, 14, 39, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (30, 15, 40, NULL, NULL, true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (31, 15, 44, 11, '<4284D7D1.6010208@xxxxxx>', true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (32, 15, 45, 11, '<20050517185429.GB20786@xxxxxxxxxxxxxxx>', true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (33, 15, 46, 11, '<428A44E9.6090802@xxxxxx>', true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (34, 15, 47, 11, '<20050517202044.GA23231@xxxxxxxxxxxxxxx>', true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (35, 15, 48, 11, '<20050617140011.GA15638@xxxxxxxxx>', true, NULL);
+INSERT INTO bugmessage (id, bug, message, bugwatch, remote_comment_id, visible, index) VALUES (36, 15, 49, 11, '<42BD2E36.9090809@xxxxxx>', true, NULL);
 
 
 ALTER TABLE bugmessage ENABLE TRIGGER ALL;
@@ -3674,6 +3704,45 @@
 ALTER TABLE bugsubscription ENABLE TRIGGER ALL;
 
 
+ALTER TABLE structuralsubscription DISABLE TRIGGER ALL;
+
+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (1, NULL, NULL, NULL, NULL, 1, NULL, 1, 16, 16, 40, 10, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (2, NULL, NULL, NULL, NULL, 1, NULL, 14, 16, 16, 40, 10, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (3, 22, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, 10, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
+INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (4, 16, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, 10, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
+
+
+ALTER TABLE structuralsubscription ENABLE TRIGGER ALL;
+
+
+ALTER TABLE bugsubscriptionfilter DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE bugsubscriptionfilter ENABLE TRIGGER ALL;
+
+
+ALTER TABLE bugsubscriptionfilterimportance DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE bugsubscriptionfilterimportance ENABLE TRIGGER ALL;
+
+
+ALTER TABLE bugsubscriptionfilterstatus DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE bugsubscriptionfilterstatus ENABLE TRIGGER ALL;
+
+
+ALTER TABLE bugsubscriptionfiltertag DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE bugsubscriptionfiltertag ENABLE TRIGGER ALL;
+
+
 ALTER TABLE bugtag DISABLE TRIGGER ALL;
 
 INSERT INTO bugtag (id, bug, tag) VALUES (1, 9, 'crash');
@@ -3735,6 +3804,27 @@
 ALTER TABLE bugtrackeralias ENABLE TRIGGER ALL;
 
 
+ALTER TABLE bugtrackercomponentgroup DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE bugtrackercomponentgroup ENABLE TRIGGER ALL;
+
+
+ALTER TABLE distributionsourcepackage DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE distributionsourcepackage ENABLE TRIGGER ALL;
+
+
+ALTER TABLE bugtrackercomponent DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE bugtrackercomponent ENABLE TRIGGER ALL;
+
+
 ALTER TABLE bugtrackerperson DISABLE TRIGGER ALL;
 
 
@@ -3936,6 +4026,13 @@
 ALTER TABLE distributionbounty ENABLE TRIGGER ALL;
 
 
+ALTER TABLE distributionjob DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE distributionjob ENABLE TRIGGER ALL;
+
+
 ALTER TABLE distributionmirror DISABLE TRIGGER ALL;
 
 INSERT INTO distributionmirror (id, distribution, name, http_base_url, ftp_base_url, rsync_base_url, displayname, description, owner, speed, country, content, official_candidate, enabled, date_created, whiteboard, status, date_reviewed, reviewer, country_dns_mirror) VALUES (1, 1, 'archive-mirror', 'http://localhost:11375/valid-mirror/', NULL, NULL, NULL, NULL, 1, 10, 75, 1, true, true, '2006-10-16 18:31:43.434567', NULL, 30, NULL, NULL, false);
@@ -3953,13 +4050,6 @@
 ALTER TABLE distributionmirror ENABLE TRIGGER ALL;
 
 
-ALTER TABLE distributionsourcepackage DISABLE TRIGGER ALL;
-
-
-
-ALTER TABLE distributionsourcepackage ENABLE TRIGGER ALL;
-
-
 ALTER TABLE distributionsourcepackagecache DISABLE TRIGGER ALL;
 
 INSERT INTO distributionsourcepackagecache (id, distribution, sourcepackagename, name, binpkgnames, binpkgsummaries, binpkgdescriptions, fti, changelog, archive) VALUES (1, 3, 19, 'alsa-utils', '', '', '', NULL, NULL, 1);
@@ -4000,6 +4090,27 @@
 ALTER TABLE distrocomponentuploader ENABLE TRIGGER ALL;
 
 
+ALTER TABLE packagediff DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE packagediff ENABLE TRIGGER ALL;
+
+
+ALTER TABLE distroseriesdifference DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE distroseriesdifference ENABLE TRIGGER ALL;
+
+
+ALTER TABLE distroseriesdifferencemessage DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE distroseriesdifferencemessage ENABLE TRIGGER ALL;
+
+
 ALTER TABLE distroserieslanguage DISABLE TRIGGER ALL;
 
 INSERT INTO distroserieslanguage (id, distroseries, language, currentcount, updatescount, rosettacount, contributorcount, dateupdated, unreviewed_count) VALUES (1, 3, 68, 62, 0, 0, 1, '2007-01-15 17:58:40.839938', 0);
@@ -4489,6 +4600,13 @@
 ALTER TABLE hwtestanswerdevice ENABLE TRIGGER ALL;
 
 
+ALTER TABLE incrementaldiff DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE incrementaldiff ENABLE TRIGGER ALL;
+
+
 ALTER TABLE ircid DISABLE TRIGGER ALL;
 
 INSERT INTO ircid (id, person, network, nickname) VALUES (1, 1, 'irc.freenode.net', 'mark');
@@ -5986,13 +6104,6 @@
 ALTER TABLE packagecopyrequest ENABLE TRIGGER ALL;
 
 
-ALTER TABLE packagediff DISABLE TRIGGER ALL;
-
-
-
-ALTER TABLE packagediff ENABLE TRIGGER ALL;
-
-
 ALTER TABLE packageselection DISABLE TRIGGER ALL;
 
 
@@ -10462,17 +10573,6 @@
 ALTER TABLE standardshipitrequest ENABLE TRIGGER ALL;
 
 
-ALTER TABLE structuralsubscription DISABLE TRIGGER ALL;
-
-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (1, NULL, NULL, NULL, NULL, 1, NULL, 1, 16, 16, 40, 10, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (2, NULL, NULL, NULL, NULL, 1, NULL, 14, 16, 16, 40, 10, '2008-01-29 15:12:34.581468', '2008-01-29 15:12:34.581468');
-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (3, 22, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, 10, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
-INSERT INTO structuralsubscription (id, product, productseries, project, milestone, distribution, distroseries, sourcepackagename, subscriber, subscribed_by, bug_notification_level, blueprint_notification_level, date_created, date_last_updated) VALUES (4, 16, NULL, NULL, NULL, NULL, NULL, NULL, 64, 64, 40, 10, '2008-02-06 12:17:13.030376', '2008-02-06 12:17:13.030376');
-
-
-ALTER TABLE structuralsubscription ENABLE TRIGGER ALL;
-
-
 ALTER TABLE suggestivepotemplate DISABLE TRIGGER ALL;
 
 
@@ -11081,6 +11181,13 @@
 ALTER TABLE translationtemplateitem ENABLE TRIGGER ALL;
 
 
+ALTER TABLE translationtemplatesbuild DISABLE TRIGGER ALL;
+
+
+
+ALTER TABLE translationtemplatesbuild ENABLE TRIGGER ALL;
+
+
 ALTER TABLE translator DISABLE TRIGGER ALL;
 
 INSERT INTO translator (id, translationgroup, language, translator, datecreated, style_guide_url) VALUES (1, 1, 387, 53, '2005-07-13 13:14:19.748396', NULL);

=== modified file 'database/schema/comments.sql'
--- database/schema/comments.sql	2010-09-03 16:43:11 +0000
+++ database/schema/comments.sql	2010-09-29 09:44:52 +0000
@@ -346,6 +346,21 @@
 COMMENT ON COLUMN BugTrackerPerson.name IS 'The (within the bug tracker) unique username in the external bug tracker.';
 COMMENT ON COLUMN BugTrackerPerson.person IS 'The Person record in Launchpad this user corresponds to.';
 
+-- BugTrackerComponent
+
+COMMENT ON TABLE BugTrackerComponent IS 'A software component in a remote bug tracker, which can be linked to the corresponding source package in a distribution using this table.';
+COMMENT ON COLUMN BugTrackerComponent.name IS 'The name of the component as registered in the remote bug tracker.';
+COMMENT ON COLUMN BugTrackerComponent.is_visible IS 'Whether to display or hide the item in the Launchpad user interface.';
+COMMENT ON COLUMN BugTrackerComponent.is_custom IS 'Whether the item was added by a user in Launchpad or is kept in sync with the remote bug tracker.';
+COMMENT ON COLUMN BugTrackerComponent.component_group IS 'The product or other higher level category used by the remote bug tracker to group projects, if any.';
+COMMENT ON COLUMN BugTrackerComponent.distro_source_package IS 'A link to the source package in a distribution that corresponds to this component.  This can be undefined if no link has been established yet.';
+
+-- BugTrackerComponentGroup
+
+COMMENT ON TABLE BugTrackerComponentGroup IS 'A collection of components as modeled in a remote bug tracker, often referred to as a product.  Some bug trackers do not categorize software components this way, so they will have a single default component group that all components belong to.';
+COMMENT ON COLUMN BugTrackerComponentGroup.name IS 'The product or category name used in the remote bug tracker for grouping components.';
+COMMENT ON COLUMN BugTrackerComponentGroup.bug_tracker IS 'The external bug tracker this component group belongs to.';
+
 -- BugCve
 
 COMMENT ON TABLE BugCve IS 'A table that records the link between a given malone bug number, and a CVE entry.';
@@ -532,9 +547,12 @@
 COMMENT ON TABLE DistroSeriesDifference IS 'A difference of versions for a package in a derived distroseries and its parent distroseries.';
 COMMENT ON COLUMN DistroSeriesDifference.derived_series IS 'The derived distroseries with the difference from its parent.';
 COMMENT ON COLUMN DistroSeriesDifference.source_package_name IS 'The name of the source package which is different in the two series.';
-COMMENT ON COLUMN DistroSeriesDifference.package_diff IS 'The most recent package diff that was created for this difference.';
+COMMENT ON COLUMN DistroSeriesDifference.package_diff IS 'The most recent package diff that was created for the base version to derived version.';
+COMMENT ON COLUMN DistroSeriesDifference.parent_package_diff IS 'The most recent package diff that was created for the base version to the parent version.';
 COMMENT ON COLUMN DistroSeriesDifference.status IS 'A distroseries difference can be needing attention, ignored or resolved.';
 COMMENT ON COLUMN DistroSeriesDifference.difference_type IS 'The type of difference that this record represents - a package unique to the derived series, or missing, or in both.';
+COMMENT ON COLUMN DistroSeriesDifference.source_version IS 'The version of the package in the derived series.';
+COMMENT ON COLUMN DistroSeriesDifference.parent_source_version IS 'The version of the package in the parent series.';
 
 -- DistroSeriesDifferenceMessage
 COMMENT ON TABLE DistroSeriesDifferenceMessage IS 'A message/comment on a distro series difference.';
@@ -839,6 +857,11 @@
 COMMENT ON COLUMN TranslationRelicensingAgreement.allow_relicensing IS 'Does this person want their translations relicensed under BSD.';
 COMMENT ON COLUMN TranslationRelicensingAgreement.date_decided IS 'Date when the last change of opinion was registered.';
 
+-- TranslationTemplatesBuild
+COMMENT ON TABLE TranslationTemplatesBuild IS 'Build-farm record of a translation templates build.';
+COMMENT ON COLUMN TranslationTemplatesBuild.build_farm_job IS 'Associated BuildFarmJob.';
+COMMENT ON COLUMN TranslationTemplatesBuild.branch IS 'Branch to build templates out of.';
+
 -- RevisionAuthor
 COMMENT ON TABLE RevisionAuthor IS 'All distinct authors for revisions.';
 COMMENT ON COLUMN RevisionAuthor.name IS 'The exact text extracted from the branch revision.';
@@ -1171,6 +1194,7 @@
 COMMENT ON COLUMN DistroArchSeries.package_count IS 'A cache of the number of binary packages published in this distro arch release. The count only includes packages published in the release pocket.';
 COMMENT ON COLUMN DistroArchSeries.supports_virtualized IS 'Whether or not
 virtualized build support should be provided by this specific distroarchseries';
+COMMENT ON COLUMN DistroArchSeries.enabled IS 'Whether to allow build creation and publishing for this DistroArchSeries.';
 
 -- LauncpadDatabaseRevision
 COMMENT ON TABLE LaunchpadDatabaseRevision IS 'This table contains a list of the database patches that have been successfully applied to this database.';
@@ -1288,11 +1312,14 @@
 
 COMMENT ON TABLE ProjectBounty IS 'This table records a simple link between a bounty and a project. This bounty will be listed on the project web page, and the project will be mentioned on the bounty web page.';
 
--- Messaging subsytem
+-- BugMessages
 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
 COMMENT ON TABLE Message IS 'This table stores a single RFC822-style message. Messages can be threaded (using the parent field). These messages can then be referenced from elsewhere in the system, such as the BugMessage table, integrating messageboard facilities with the rest of The Launchpad.';
 COMMENT ON COLUMN Message.parent IS 'A "parent message". This allows for some level of threading in Messages.';
 COMMENT ON COLUMN Message.subject IS 'The title text of the message, or the subject if it was an email.';
@@ -1932,6 +1959,14 @@
 COMMENT ON COLUMN Continent.code IS 'A two-letter code for a continent.';
 COMMENT ON COLUMN Continent.name IS 'The name of the continent.';
 
+-- DistributionJob
+
+COMMENT ON TABLE DistributionJob IS 'Contains references to jobs to be run on distributions.';
+COMMENT ON COLUMN DistributionJob.distribution IS 'The distribution to be acted on.';
+COMMENT ON COLUMN DistributionJob.distroseries IS 'The distroseries to be acted on.';
+COMMENT ON COLUMN DistributionJob.job_type IS 'The type of job';
+COMMENT ON COLUMN DistributionJob.json_data IS 'A JSON struct containing data for the job.';
+
 -- DistributionMirror
 COMMENT ON TABLE DistributionMirror IS 'A mirror of a given distribution.';
 COMMENT ON COLUMN DistributionMirror.distribution IS 'The distribution to which the mirror refers to.';
@@ -2303,6 +2338,14 @@
 COMMENT ON COLUMN HWDMIValue.value IS 'The value';
 COMMENT ON COLUMN HWDMIValue.handle IS 'The handle to which this key/value pair belongs.';
 
+-- IncrementalDiff
+COMMENT ON TABLE IncrementalDiff IS 'Incremental diffs for merge proposals.';
+COMMENT ON COLUMN IncrementalDiff.diff IS 'The contents of the diff.';
+COMMENT ON COLUMN IncrementalDiff.branch_merge_proposal IS 'The merge proposal the diff is for.';
+COMMENT ON COLUMN IncrementalDiff.old_revision IS 'The revision the diff is from.';
+COMMENT ON COLUMN IncrementalDiff.new_revision IS 'The revision the diff is to.';
+
+
 -- Job
 
 COMMENT ON TABLE Job IS 'Common info about a job.';

=== modified file 'database/schema/launchpad_session.sql'
--- database/schema/launchpad_session.sql	2010-09-06 09:48:41 +0000
+++ database/schema/launchpad_session.sql	2010-09-29 09:44:52 +0000
@@ -17,14 +17,13 @@
 CREATE TABLE TimeLimitedToken (
     path text NOT NULL,
     token text NOT NULL,
-    created timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    constraint timelimitedtoken_pky primary key (path, token)
+    created timestamp without time zone
+        NOT NULL DEFAULT (CURRENT_TIMESTAMP AT TIME ZONE 'UTC'),
+    constraint timelimitedtoken_pkey primary key (path, token)
     ) WITHOUT OIDS;
 COMMENT ON TABLE TimeLimitedToken IS 'stores tokens for granting access to a single path in the librarian for a short while. The garbo takes care of cleanups, and we should only have a few thousand at a time. Tokens are handed out just-in-time on the appserver, when a client attempts to dereference a private thing which we do not want to deliver in-line. OAuth tokens cannot be used for the launchpadlibrarian content because they would then be attackable. See lib.canonical.database.librarian for the python class.';
 -- Give the garbo an efficient selection to cleanup
 CREATE INDEX timelimitedtoken_created ON TimeLimitedToken(created);
--- Give the librarian an efficient lookup
-CREATE INDEX timelimitedtoken_path_token ON TimeLimitedToken(path, token);
 
 -- Let the session user access file access tokens.
 GRANT SELECT, INSERT, UPDATE, DELETE ON TimeLimitedToken TO session;

=== added file 'database/schema/patch-2208-08-1.sql'
--- database/schema/patch-2208-08-1.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-08-1.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,9 @@
+-- Copyright 2010 Canonical Ltd.  This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+ALTER TABLE distroarchseries
+    ADD COLUMN enabled bool NOT NULL DEFAULT TRUE;
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 08, 1);

=== added file 'database/schema/patch-2208-08-2.sql'
--- database/schema/patch-2208-08-2.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-08-2.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,8 @@
+SET client_min_messages=ERROR;
+
+-- Permit bug searches ordered by 'importance' - the default - to serve from
+-- index rather than doing the full search and sorting.
+
+CREATE INDEX bugtask_importance_idx ON BugTask (importance, id desc nulls first);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 8, 2);

=== added file 'database/schema/patch-2208-08-3.sql'
--- database/schema/patch-2208-08-3.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-08-3.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,8 @@
+SET client_min_messages=ERROR;
+
+-- Permit bug searches ordered by 'date_closed desc, id' to serve from
+-- index rather than doing the full search and sorting.
+
+CREATE INDEX bugtask__date_closed__id__idx2 ON BugTask (date_closed, id desc nulls first);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 8, 3);

=== added file 'database/schema/patch-2208-09-0.sql'
--- database/schema/patch-2208-09-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-09-0.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,38 @@
+-- Copyright 2010 Canonical Ltd.  This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+CREATE TABLE BugTrackerComponentGroup (
+    id serial PRIMARY KEY,
+    name text NOT NULL,
+    bug_tracker integer NOT NULL REFERENCES BugTracker
+
+    CONSTRAINT valid_name CHECK (valid_name(name))
+);
+
+ALTER TABLE BugTrackerComponentGroup
+    ADD CONSTRAINT bugtrackercomponentgroup__bug_tracker__name__key
+    UNIQUE (bug_tracker, name);
+
+
+CREATE TABLE BugTrackerComponent (
+    id serial PRIMARY KEY,
+    name text NOT NULL,
+    is_visible boolean NOT NULL DEFAULT True,
+    is_custom boolean NOT NULL DEFAULT True,
+    component_group integer NOT NULL REFERENCES BugTrackerComponentGroup,
+    distro_source_package integer REFERENCES DistributionSourcePackage,
+
+    CONSTRAINT valid_name CHECK (valid_name(name))
+);
+
+ALTER TABLE BugTrackerComponent
+    ADD CONSTRAINT bugtrackercomponent__component_group__name__key
+    UNIQUE (component_group, name);
+
+ALTER TABLE BugTrackerComponent
+    ADD CONSTRAINT bugtrackercomponent__distro_source_package__key
+    UNIQUE (distro_source_package);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES(2208, 09, 0);

=== added file 'database/schema/patch-2208-10-0.sql'
--- database/schema/patch-2208-10-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-10-0.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,25 @@
+-- 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;
+
+-- The `InitialiseDistroSeriesJob` table captures the data required for an ifp job.
+
+CREATE TABLE DistributionJob (
+    id serial PRIMARY KEY,
+    -- FK to the `Job` record with the "generic" data about this archive
+    -- job.
+    job integer NOT NULL CONSTRAINT distributionjob__job__fk REFERENCES job,
+    -- FK to the associated `Distribution` record.
+    distribution integer NOT NULL REFERENCES Distribution,
+    distroseries integer REFERENCES DistroSeries,
+    -- The particular type of foo job
+    job_type integer NOT NULL,
+    -- JSON data for use by the job
+    json_data text
+);
+
+ALTER TABLE DistributionJob ADD CONSTRAINT distributionjob__job__key UNIQUE (job);
+CREATE UNIQUE INDEX distribution_job__initialise_series__distroseries ON DistributionJob (distroseries) WHERE job_type = 1;
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 10, 0);
+

=== added file 'database/schema/patch-2208-11-0.sql'
--- database/schema/patch-2208-11-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-11-0.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,30 @@
+-- Copyright 2010 Canonical Ltd.  This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+-- This constraint is useless given the current PK
+ALTER TABLE BranchRevision
+DROP CONSTRAINT revisionnumber_branch_id_unique;
+
+-- This constraint is no longer needed as it will be the new PK
+ALTER TABLE BranchRevision
+DROP CONSTRAINT revision__branch__revision__key ;
+
+-- Kill the old PK
+ALTER TABLE BranchRevision
+DROP CONSTRAINT revisionnumber_pkey ;
+
+-- Create the new PK
+ALTER TABLE BranchRevision
+ADD CONSTRAINT revisionnumber_pkey PRIMARY KEY (branch, revision);
+
+-- What was this used for?  Not used now.
+DROP VIEW RevisionNumber;
+
+-- Kill the old id.
+ALTER TABLE BranchRevision
+DROP COLUMN id;
+
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 11, 0);

=== added file 'database/schema/patch-2208-12-0.sql'
--- database/schema/patch-2208-12-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-12-0.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,17 @@
+SET client_min_messages=ERROR;
+
+-- Allow for package diffs against both derived and parent versions.
+ALTER TABLE DistroSeriesDifference ADD COLUMN parent_package_diff integer CONSTRAINT distroseriesdifference__parent_package_diff__fk REFERENCES packagediff;
+CREATE INDEX distroseriesdifference__parent_package_diff__idx ON distroseriesdifference(parent_package_diff);
+
+-- Add columns for source_version and parent_source_version
+ALTER TABLE DistroSeriesDifference ADD COLUMN source_version text;
+ALTER TABLE DistroSeriesDifference ADD CONSTRAINT valid_source_version CHECK(valid_debian_version(source_version));
+ALTER TABLE DistroSeriesDifference ADD COLUMN parent_source_version text;
+ALTER TABLE DistroSeriesDifference ADD CONSTRAINT valid_parent_source_version CHECK(valid_debian_version(parent_source_version));
+
+-- Add a unique constraint/index for the source_package_name/derived series combo and drop the previous index.
+ALTER TABLE DistroSeriesDifference ADD CONSTRAINT distroseriesdifference__derived_series__source_package_name__key UNIQUE (derived_series, source_package_name);
+DROP INDEX distroseriesdifference__derived_series__idx;
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 12, 0);

=== added file 'database/schema/patch-2208-13-0.sql'
--- database/schema/patch-2208-13-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-13-0.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,17 @@
+-- Copyright 2010 Canonical Ltd.  This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+CREATE TABLE TranslationTemplatesBuild (
+    id SERIAL PRIMARY KEY,
+    build_farm_job integer NOT NULL REFERENCES BuildFarmJob(id),
+    branch integer NOT NULL REFERENCES Branch(id));
+
+CREATE INDEX translationtemplatesbuild__build_farm_job__idx ON
+    TranslationTemplatesBuild(build_farm_job);
+
+CREATE INDEX translationtemplatesbuild__branch__idx ON
+    TranslationTemplatesBuild(branch);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 13, 0);

=== added file 'database/schema/patch-2208-14-0.sql'
--- database/schema/patch-2208-14-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-14-0.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,9 @@
+SET client_min_messages=ERROR;
+
+-- Store the row index of bug messages so we don't have to calculate it all the time.
+ALTER TABLE BugMessage ADD COLUMN index integer;
+
+-- BugMessage.indexes must be unique per bug.
+ALTER TABLE BugMessage ADD CONSTRAINT bugmessage__bug__index__key UNIQUE (bug, index);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 14, 0);

=== added file 'database/schema/patch-2208-15-0.sql'
--- database/schema/patch-2208-15-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-15-0.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,7 @@
+SET client_min_messages=ERROR;
+
+-- Delete index obsoleted by bugtask__date_closed__id__idx2
+
+DROP INDEX bugtask__date_closed__id__idx;
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 15, 0);

=== added file 'database/schema/patch-2208-16-0.sql'
--- database/schema/patch-2208-16-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-16-0.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,8 @@
+-- Copyright 2010 Canonical Ltd.  This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+SET client_min_messages=ERROR;
+
+ALTER TABLE SourcePackageRecipeBuildJob
+    ALTER COLUMN sourcepackage_recipe_build SET NOT NULL;
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 16, 0);

=== added file 'database/schema/patch-2208-17-0.sql'
--- database/schema/patch-2208-17-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-17-0.sql	2010-09-29 09:44:52 +0000
@@ -0,0 +1,18 @@
+-- Copyright 2010 Canonical Ltd. This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+CREATE TABLE IncrementalDiff(
+    id serial PRIMARY KEY,
+    diff integer NOT NULL CONSTRAINT diff_fk REFERENCES Diff ON DELETE CASCADE,
+    branch_merge_proposal integer NOT NULL CONSTRAINT branch_merge_proposal_fk REFERENCES BranchMergeProposal ON DELETE CASCADE,
+    old_revision integer NOT NULL CONSTRAINT old_revision_fk REFERENCES Revision ON DELETE CASCADE,
+    new_revision integer NOT NULL CONSTRAINT new_revision_fk REFERENCES Revision ON DELETE CASCADE);
+
+CREATE INDEX incrementaldiff__diff__idx ON IncrementalDiff(diff);
+CREATE INDEX incrementaldiff__branch_merge_proposal__idx ON IncrementalDiff(branch_merge_proposal);
+CREATE INDEX incrementaldiff__old_revision__idx ON IncrementalDiff(old_revision);
+CREATE INDEX incrementaldiff__new_revision__idx ON IncrementalDiff(new_revision);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 17, 0);

=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg	2010-09-21 23:09:59 +0000
+++ database/schema/security.cfg	2010-09-29 09:44:52 +0000
@@ -138,6 +138,7 @@
 public.databasereplicationlag           = SELECT
 public.diff                             = SELECT, INSERT, UPDATE
 public.distributionbounty               = SELECT, INSERT, UPDATE
+public.distributionjob                  = SELECT, INSERT
 public.distributionmirror               = SELECT, INSERT, UPDATE, DELETE
 public.distributionsourcepackage        = SELECT, INSERT, UPDATE, DELETE
 public.distributionsourcepackagecache   = SELECT
@@ -169,6 +170,7 @@
 public.hwtest                           = SELECT
 public.hwvendorid                       = SELECT
 public.hwvendorname                     = SELECT
+public.incrementaldiff                  = SELECT, INSERT, UPDATE, DELETE
 public.job                              = SELECT, INSERT, UPDATE, DELETE
 public.karmacache                       = SELECT
 public.karmacategory                    = SELECT
@@ -237,7 +239,6 @@
 public.revision                         = SELECT, INSERT, UPDATE
 public.revisionauthor                   = SELECT, INSERT, UPDATE
 public.revisioncache                    = SELECT, INSERT, UPDATE, DELETE
-public.revisionnumber                   = SELECT, INSERT
 public.revisionparent                   = SELECT, INSERT
 public.scriptactivity                   = SELECT
 public.shipitreport                     = SELECT, INSERT
@@ -276,6 +277,7 @@
 public.translationgroup                 = SELECT, INSERT, UPDATE
 public.translationimportqueueentry      = SELECT, INSERT, UPDATE, DELETE
 public.translationmessage               = SELECT, INSERT, UPDATE
+public.translationtemplatesbuild        = SELECT, INSERT, UPDATE, DELETE
 public.translator                       = SELECT, INSERT, UPDATE, DELETE
 public.usertouseremail                  = SELECT, UPDATE
 public.validpersoncache                 = SELECT
@@ -538,6 +540,10 @@
 public.bugnotification                  = SELECT, INSERT
 public.bugnotificationrecipient         = SELECT, INSERT
 public.bugsubscription                  = SELECT
+public.bugsubscriptionfilter            = SELECT
+public.bugsubscriptionfilterstatus      = SELECT
+public.bugsubscriptionfilterimportance  = SELECT
+public.bugsubscriptionfiltertag         = SELECT
 public.bugtask                          = SELECT, INSERT, UPDATE
 public.bugtracker                       = SELECT, INSERT
 public.bugtrackeralias                  = SELECT
@@ -595,7 +601,9 @@
 public.distribution                     = SELECT
 public.distributionsourcepackage        = SELECT, UPDATE
 public.emailaddress                     = SELECT
+public.incrementaldiff                  = SELECT
 public.job                              = SELECT, INSERT, UPDATE, DELETE
+public.translationtemplatesbuild        = SELECT, INSERT
 # Karma
 public.karma                            = SELECT, INSERT
 public.karmaaction                      = SELECT
@@ -617,6 +625,10 @@
 public.bugactivity                      = SELECT, INSERT
 public.bugaffectsperson                 = SELECT, INSERT, UPDATE, DELETE
 public.bugsubscription                  = SELECT
+public.bugsubscriptionfilter            = SELECT
+public.bugsubscriptionfilterstatus      = SELECT
+public.bugsubscriptionfilterimportance  = SELECT
+public.bugsubscriptionfiltertag         = SELECT
 public.bugnotification                  = SELECT, INSERT
 public.bugnotificationrecipient         = SELECT, INSERT
 public.structuralsubscription           = SELECT
@@ -805,6 +817,10 @@
 public.bugactivity                      = SELECT, INSERT
 public.bugaffectsperson                 = SELECT, INSERT, UPDATE, DELETE
 public.bugsubscription                  = SELECT
+public.bugsubscriptionfilter            = SELECT
+public.bugsubscriptionfilterstatus      = SELECT
+public.bugsubscriptionfilterimportance  = SELECT
+public.bugsubscriptionfiltertag         = SELECT
 public.bugnotification                  = SELECT, INSERT
 public.bugnotificationrecipient         = SELECT, INSERT
 public.bugnomination                    = SELECT
@@ -889,6 +905,7 @@
 public.flatpackagesetinclusion                  = SELECT
 public.teamparticipation                        = SELECT
 public.translationimportqueueentry              = SELECT, INSERT, UPDATE
+public.translationtemplatesbuild                = SELECT, INSERT
 
 [ppa-apache-log-parser]
 type=user
@@ -929,6 +946,10 @@
 public.bugpackageinfestation            = SELECT, INSERT, UPDATE
 public.bugproductinfestation            = SELECT, INSERT, UPDATE
 public.bugsubscription                  = SELECT, INSERT, UPDATE, DELETE
+public.bugsubscriptionfilter            = SELECT, INSERT, UPDATE, DELETE
+public.bugsubscriptionfilterstatus      = SELECT, INSERT, UPDATE, DELETE
+public.bugsubscriptionfilterimportance  = SELECT, INSERT, UPDATE, DELETE
+public.bugsubscriptionfiltertag         = SELECT, INSERT, UPDATE, DELETE
 public.bugtask                          = SELECT, INSERT, UPDATE, DELETE
 public.bugtracker                       = SELECT, INSERT, UPDATE, DELETE
 public.bugtrackeralias                  = SELECT, INSERT, UPDATE, DELETE
@@ -1157,6 +1178,10 @@
 public.bugaffectsperson                 = SELECT, INSERT, UPDATE, DELETE
 public.bugjob                           = SELECT, INSERT
 public.bugsubscription                  = SELECT
+public.bugsubscriptionfilter            = SELECT
+public.bugsubscriptionfilterstatus      = SELECT
+public.bugsubscriptionfilterimportance  = SELECT
+public.bugsubscriptionfiltertag         = SELECT
 public.bugnotification                  = SELECT, INSERT
 public.bugnotificationrecipient         = SELECT, INSERT
 public.bugnomination                    = SELECT
@@ -1259,6 +1284,10 @@
 public.bugaffectsperson                 = SELECT, INSERT, UPDATE, DELETE
 public.bugjob                           = SELECT, INSERT
 public.bugsubscription                  = SELECT
+public.bugsubscriptionfilter            = SELECT
+public.bugsubscriptionfilterstatus      = SELECT
+public.bugsubscriptionfilterimportance  = SELECT
+public.bugsubscriptionfiltertag         = SELECT
 public.bugnotification                  = SELECT, INSERT
 public.bugnotificationrecipient         = SELECT, INSERT
 public.bugnomination                    = SELECT
@@ -1326,6 +1355,10 @@
 public.bugnotification                  = SELECT, INSERT, UPDATE
 public.bugnotificationrecipient         = SELECT, INSERT, UPDATE
 public.bugsubscription                  = SELECT, INSERT
+public.bugsubscriptionfilter            = SELECT, INSERT
+public.bugsubscriptionfilterstatus      = SELECT, INSERT
+public.bugsubscriptionfilterimportance  = SELECT, INSERT
+public.bugsubscriptionfiltertag         = SELECT, INSERT
 public.bugnomination                    = SELECT
 public.bug                              = SELECT, INSERT, UPDATE
 public.bugactivity                      = SELECT, INSERT
@@ -1545,6 +1578,10 @@
 public.bugaffectsperson                 = SELECT, INSERT, UPDATE, DELETE
 public.bugjob                           = SELECT, INSERT
 public.bugsubscription                  = SELECT, INSERT
+public.bugsubscriptionfilter            = SELECT, INSERT
+public.bugsubscriptionfilterstatus      = SELECT, INSERT
+public.bugsubscriptionfilterimportance  = SELECT, INSERT
+public.bugsubscriptionfiltertag         = SELECT, INSERT
 public.bugnotification                  = SELECT, INSERT
 public.bugnotificationattachment        = SELECT
 public.bugnotificationrecipient         = SELECT, INSERT
@@ -1553,6 +1590,10 @@
 public.bugtask                          = SELECT, INSERT, UPDATE
 public.bugmessage                       = SELECT, INSERT
 public.bugsubscription                  = SELECT, INSERT, UPDATE, DELETE
+public.bugsubscriptionfilter            = SELECT, INSERT, UPDATE, DELETE
+public.bugsubscriptionfilterstatus      = SELECT, INSERT, UPDATE, DELETE
+public.bugsubscriptionfilterimportance  = SELECT, INSERT, UPDATE, DELETE
+public.bugsubscriptionfiltertag         = SELECT, INSERT, UPDATE, DELETE
 public.bugtracker                       = SELECT, INSERT
 public.bugtrackeralias                  = SELECT, INSERT
 public.bugwatch                         = SELECT, INSERT
@@ -1719,6 +1760,7 @@
 public.distribution                     = SELECT
 public.distroseries                     = SELECT
 public.emailaddress                     = SELECT
+public.incrementaldiff                  = SELECT, INSERT
 public.job                              = SELECT, INSERT, UPDATE
 public.karmaaction                      = SELECT
 public.karma                            = SELECT, INSERT
@@ -1731,6 +1773,7 @@
 public.previewdiff                      = SELECT, INSERT
 public.product                          = SELECT
 public.productseries                    = SELECT
+public.revision                         = SELECT
 public.seriessourcepackagebranch        = SELECT
 public.sourcepackagename                = SELECT
 public.staticdiff                       = SELECT, INSERT
@@ -1812,6 +1855,10 @@
 public.message                          = SELECT, INSERT
 public.messagechunk                     = SELECT, INSERT
 public.bugsubscription                  = SELECT, INSERT
+public.bugsubscriptionfilter            = SELECT, INSERT
+public.bugsubscriptionfilterstatus      = SELECT, INSERT
+public.bugsubscriptionfilterimportance  = SELECT, INSERT
+public.bugsubscriptionfiltertag         = SELECT, INSERT
 public.bugmessage                       = SELECT, INSERT
 public.sourcepackagename                = SELECT
 public.job                              = SELECT, INSERT, UPDATE
@@ -1839,6 +1886,10 @@
 public.bug                              = SELECT, UPDATE
 public.bugattachment                    = SELECT, DELETE
 public.bugsubscription                  = SELECT
+public.bugsubscriptionfilter            = SELECT
+public.bugsubscriptionfilterstatus      = SELECT
+public.bugsubscriptionfilterimportance  = SELECT
+public.bugsubscriptionfiltertag         = SELECT
 public.bugaffectsperson                 = SELECT
 public.bugnotification                  = SELECT, DELETE
 public.bugnotificationrecipientarchive  = SELECT

=== modified file 'lib/canonical/config/schema-lazr.conf'
--- lib/canonical/config/schema-lazr.conf	2010-09-24 14:24:06 +0000
+++ lib/canonical/config/schema-lazr.conf	2010-09-29 09:44:52 +0000
@@ -644,6 +644,11 @@
 # datatype: integer
 storm_cache_size: 500
 
+# Where database/replication/slon_ctl.py dumps its logs. Used for the
+# staging replication environment.
+# datatype: existing_directory
+replication_logdir: database/replication
+
 
 [diff]
 # The maximum size in bytes to read from the librarian to make available in

=== modified file 'lib/canonical/launchpad/icing/style-3-0.css.in'
--- lib/canonical/launchpad/icing/style-3-0.css.in	2010-09-10 17:32:29 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css.in	2010-09-29 09:44:52 +0000
@@ -2419,7 +2419,7 @@
     background-repeat: no-repeat;
     background-position:right center;
     }
-table#packages_list tr.superseded {
+table#packages_list tr.superseded, tr.blacklisted {
     background-color: #eee;
     }
 ul.latest-ppa-updates li:nth-child(odd) {

=== modified file 'lib/canonical/launchpad/interfaces/__init__.py'
--- lib/canonical/launchpad/interfaces/__init__.py	2010-09-11 19:25:13 +0000
+++ lib/canonical/launchpad/interfaces/__init__.py	2010-09-29 09:44:52 +0000
@@ -55,6 +55,8 @@
 from lp.registry.interfaces.distribution import *
 from lp.registry.interfaces.distributionmirror import *
 from lp.registry.interfaces.distributionsourcepackage import *
+from lp.registry.interfaces.distroseriesdifference import *
+from lp.registry.interfaces.distroseriesdifferencecomment import *
 from lp.soyuz.interfaces.distributionsourcepackagecache import *
 from lp.soyuz.interfaces.distributionsourcepackagerelease import *
 from lp.registry.interfaces.series import *

=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py	2010-08-27 11:19:54 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py	2010-09-29 09:44:52 +0000
@@ -77,6 +77,9 @@
     IDistributionSourcePackage,
     )
 from lp.registry.interfaces.distroseries import IDistroSeries
+from lp.registry.interfaces.distroseriesdifferencecomment import (
+    IDistroSeriesDifferenceComment,
+    )
 from lp.registry.interfaces.person import (
     IPerson,
     IPersonPublic,
@@ -89,6 +92,7 @@
     IStructuralSubscription,
     IStructuralSubscriptionTarget,
     )
+from lp.services.comments.interfaces.conversation import IComment
 from lp.soyuz.enums import (
     PackagePublishingStatus,
     PackageUploadCustomFormat,
@@ -321,6 +325,9 @@
 IBuildFarmJob['status'].vocabulary = BuildStatus
 IBuildFarmJob['buildqueue_record'].schema = IBuildQueue
 
+# IComment
+IComment['comment_author'].schema = IPerson
+
 # IDistribution
 IDistribution['series'].value_type.schema = IDistroSeries
 patch_reference_property(
@@ -366,6 +373,9 @@
     IDistroSeries, 'getPackageUploads', IPackageUpload)
 patch_reference_property(IDistroSeries, 'parent_series', IDistroSeries)
 
+# IDistroSeriesDifferenceComment
+IDistroSeriesDifferenceComment['comment_author'].schema = IPerson
+
 # IDistroArchSeries
 patch_reference_property(IDistroArchSeries, 'main_archive', IArchive)
 

=== modified file 'lib/canonical/launchpad/webapp/interfaces.py'
--- lib/canonical/launchpad/webapp/interfaces.py	2010-09-12 11:43:36 +0000
+++ lib/canonical/launchpad/webapp/interfaces.py	2010-09-29 09:44:52 +0000
@@ -335,6 +335,10 @@
     query_string_params = Attribute(
         'A dictionary of the query string parameters.')
 
+    is_ajax = Bool(
+        title=_('Is ajax'), required=False, readonly=True,
+        description=_("Indicates whether the request is an XMLHttpRequest."))
+
     def getRootURL(rootsite):
         """Return this request's root URL.
 

=== modified file 'lib/canonical/launchpad/webapp/servers.py'
--- lib/canonical/launchpad/webapp/servers.py	2010-09-22 15:17:39 +0000
+++ lib/canonical/launchpad/webapp/servers.py	2010-09-29 09:44:52 +0000
@@ -526,7 +526,27 @@
     return decoded_qs
 
 
-class BasicLaunchpadRequest:
+class LaunchpadBrowserRequestMixin:
+    """Provides methods used for both API and web browser requests."""
+
+    def getRootURL(self, rootsite):
+        """See IBasicLaunchpadRequest."""
+        if rootsite is not None:
+            assert rootsite in allvhosts.configs, (
+                "rootsite is %s.  Must be in %r." % (
+                    rootsite, sorted(allvhosts.configs.keys())))
+            root_url = allvhosts.configs[rootsite].rooturl
+        else:
+            root_url = self.getApplicationURL() + '/'
+        return root_url
+
+    @property
+    def is_ajax(self):
+        """See `IBasicLaunchpadRequest`."""
+        return 'XMLHttpRequest' == self.getHeader('HTTP_X_REQUESTED_WITH')
+
+
+class BasicLaunchpadRequest(LaunchpadBrowserRequestMixin):
     """Mixin request class to provide stepstogo."""
 
     implements(IBasicLaunchpadRequest)
@@ -581,24 +601,8 @@
         return get_query_string_params(self)
 
 
-class LaunchpadBrowserRequestMixin:
-    """A mixin for classes that share some method implementations."""
-
-    def getRootURL(self, rootsite):
-        """See IBasicLaunchpadRequest."""
-        if rootsite is not None:
-            assert rootsite in allvhosts.configs, (
-                "rootsite is %s.  Must be in %r." % (
-                    rootsite, sorted(allvhosts.configs.keys())))
-            root_url = allvhosts.configs[rootsite].rooturl
-        else:
-            root_url = self.getApplicationURL() + '/'
-        return root_url
-
-
 class LaunchpadBrowserRequest(BasicLaunchpadRequest, BrowserRequest,
-                              NotificationRequest, ErrorReportRequest,
-                              LaunchpadBrowserRequestMixin):
+                              NotificationRequest, ErrorReportRequest):
     """Integration of launchpad mixin request classes to make an uber
     launchpad request class.
     """
@@ -1370,7 +1374,7 @@
 
 
 class PublicXMLRPCRequest(BasicLaunchpadRequest, XMLRPCRequest,
-                          ErrorReportRequest, LaunchpadBrowserRequestMixin):
+                          ErrorReportRequest):
     """Request type for doing public XML-RPC in Launchpad."""
 
     def _createResponse(self):

=== modified file 'lib/canonical/launchpad/webapp/tests/test_servers.py'
--- lib/canonical/launchpad/webapp/tests/test_servers.py	2010-08-20 20:31:18 +0000
+++ lib/canonical/launchpad/webapp/tests/test_servers.py	2010-09-29 09:44:52 +0000
@@ -358,6 +358,20 @@
             retried_request.response.getHeader('Vary'),
             'Cookie, Authorization')
 
+    def test_is_ajax_false(self):
+        """Normal requests do not define HTTP_X_REQUESTED_WITH."""
+        request = LaunchpadBrowserRequest(StringIO.StringIO(''), {})
+
+        self.assertFalse(request.is_ajax)
+
+    def test_is_ajax_true(self):
+        """Requests with HTTP_X_REQUESTED_WITH set are ajax requests."""
+        request = LaunchpadBrowserRequest(StringIO.StringIO(''), {
+            'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest',
+            })
+
+        self.assertTrue(request.is_ajax)
+
 
 class IThingSet(Interface):
     """Marker interface for a set of things."""

=== modified file 'lib/canonical/launchpad/zcml/librarian.zcml'
--- lib/canonical/launchpad/zcml/librarian.zcml	2010-09-06 15:14:17 +0000
+++ lib/canonical/launchpad/zcml/librarian.zcml	2010-09-29 09:44:52 +0000
@@ -16,7 +16,7 @@
     <allow interface="canonical.launchpad.interfaces.ILibraryFileAliasWithParent" />
     <require
       permission="launchpad.Edit"
-      set_attributes="restricted" />
+      set_attributes="mimetype restricted" />
   </class>
 
   <class class="canonical.launchpad.database.LibraryFileContent">

=== modified file 'lib/lp/app/templates/base-layout-macros.pt'
--- lib/lp/app/templates/base-layout-macros.pt	2010-09-21 04:21:16 +0000
+++ lib/lp/app/templates/base-layout-macros.pt	2010-09-29 09:44:52 +0000
@@ -181,6 +181,8 @@
     <script type="text/javascript"
             tal:attributes="src string:${lp_js}/bugs/bugtracker_overlay.js"></script>
     <script type="text/javascript"
+            tal:attributes="src string:${lp_js}/registry/distroseriesdifferences_details.js"></script>
+    <script type="text/javascript"
             tal:attributes="src string:${lp_js}/registry/milestoneoverlay.js"></script>
     <script type="text/javascript"
             tal:attributes="src string:${lp_js}/registry/milestonetable.js"></script>

=== modified file 'lib/lp/bugs/browser/bugattachment.py'
--- lib/lp/bugs/browser/bugattachment.py	2010-09-06 07:44:21 +0000
+++ lib/lp/bugs/browser/bugattachment.py	2010-09-29 09:44:52 +0000
@@ -12,19 +12,21 @@
     'BugAttachmentURL',
     ]
 
-from cStringIO import StringIO
-
-from zope.component import getUtility
+from zope.component import (
+    getMultiAdapter,
+    getUtility,
+    )
 from zope.contenttype import guess_content_type
 from zope.interface import implements
 
 from canonical.launchpad.browser.librarian import (
     FileNavigationMixin,
     ProxiedLibraryFileAlias,
-    StreamOrRedirectLibraryFileAliasView,
     SafeStreamOrRedirectLibraryFileAliasView,
     )
-from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
+from canonical.launchpad.interfaces.librarian import (
+    ILibraryFileAliasWithParent,
+    )
 from canonical.launchpad.webapp import (
     canonical_url,
     custom_widget,
@@ -163,7 +165,10 @@
             self.context.title = data['title']
 
         if self.context.libraryfile.mimetype != data['contenttype']:
-            self.updateContentType(data['contenttype'])
+            lfa_with_parent = getMultiAdapter(
+                (self.context.libraryfile, self.context),
+                ILibraryFileAliasWithParent)
+            lfa_with_parent.mimetype = data['contenttype']
 
     @action('Delete Attachment', name='delete')
     def delete_action(self, action, data):
@@ -174,20 +179,6 @@
             url=libraryfile_url, name=self.context.title))
         self.context.removeFromBug(user=self.user)
 
-    def updateContentType(self, new_content_type):
-        """Update the attachment content type."""
-        filealiasset = getUtility(ILibraryFileAliasSet)
-        old_filealias = self.context.libraryfile
-        # Download the file and upload it again with the new content
-        # type.
-        # XXX: Bjorn Tillenius 2005-06-30:
-        # It should be possible to simply create a new filealias
-        # with the same content as the old one.
-        old_content = old_filealias.read()
-        self.context.libraryfile = filealiasset.create(
-            name=old_filealias.filename, size=len(old_content),
-            file=StringIO(old_content), contentType=new_content_type)
-
     @property
     def label(self):
         return smartquote('Bug #%d - Edit attachment "%s"') % (

=== added file 'lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py'
--- lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,107 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+import transaction
+from zope.security.interfaces import Unauthorized
+
+from canonical.testing import LaunchpadFunctionalLayer
+from lp.testing import (
+    login_person,
+    TestCaseWithFactory,
+    )
+from lp.testing.views import create_initialized_view
+
+
+class TestBugAttachmentEditView(TestCaseWithFactory):
+    """Tests of traversal to and access of files of bug attachments."""
+
+    layer = LaunchpadFunctionalLayer
+
+    CHANGE_FORM_DATA = {
+        'field.title': 'new description',
+        'field.patch': 'on',
+        'field.contenttype': 'application/whatever',
+        'field.actions.change': 'Change',
+        }
+
+    def setUp(self):
+        super(TestBugAttachmentEditView, self).setUp()
+        self.bug_owner = self.factory.makePerson()
+        login_person(self.bug_owner)
+        self.bug = self.factory.makeBug(owner=self.bug_owner)
+        self.bugattachment = self.factory.makeBugAttachment(
+            bug=self.bug, filename='foo.diff', data='file content',
+            description='attachment description', content_type='text/plain',
+            is_patch=False)
+        # The Librarian server should know about the new file before
+        # we start the tests.
+        transaction.commit()
+
+    def test_change_action_public_bug(self):
+        # Properties of attachments for public bugs can be
+        # changed by every user.
+        user = self.factory.makePerson()
+        login_person(user)
+        create_initialized_view(
+            self.bugattachment, name='+edit', form=self.CHANGE_FORM_DATA)
+        self.assertEqual('new description', self.bugattachment.title)
+        self.assertTrue(self.bugattachment.is_patch)
+        self.assertEqual(
+            'application/whatever', self.bugattachment.libraryfile.mimetype)
+
+    def test_change_action_private_bug(self):
+        # Subscribers of a private bug can edit attachments.
+        user = self.factory.makePerson()
+        self.bug.subscribe(user, self.bug_owner)
+        self.bug.setPrivate(True, self.bug_owner)
+        transaction.commit()
+        login_person(user)
+        create_initialized_view(
+            self.bugattachment, name='+edit', form=self.CHANGE_FORM_DATA)
+        self.assertEqual('new description', self.bugattachment.title)
+        self.assertTrue(self.bugattachment.is_patch)
+        self.assertEqual(
+            'application/whatever', self.bugattachment.libraryfile.mimetype)
+
+    def test_change_action_private_bug_unauthorized(self):
+        # Other users cannot edit attachments of private bugs.
+        user = self.factory.makePerson()
+        self.bug.setPrivate(True, self.bug_owner)
+        transaction.commit()
+        login_person(user)
+        self.assertRaises(
+            Unauthorized, create_initialized_view, self.bugattachment,
+            name='+edit', form=self.CHANGE_FORM_DATA)
+
+    DELETE_FORM_DATA = {
+        'field.actions.delete': 'Delete Attachment',
+        }
+
+    def test_delete_action_public_bug(self):
+        # Bug attachments can be removed from a bug.
+        user = self.factory.makePerson()
+        login_person(user)
+        create_initialized_view(
+            self.bugattachment, name='+edit', form=self.DELETE_FORM_DATA)
+        self.assertEqual(0, self.bug.attachments.count())
+
+    def test_delete_action_private_bug(self):
+        # Subscribers of a private bug can delete attachments.
+        user = self.factory.makePerson()
+        self.bug.subscribe(user, self.bug_owner)
+        self.bug.setPrivate(True, self.bug_owner)
+        login_person(user)
+        create_initialized_view(
+            self.bugattachment, name='+edit', form=self.DELETE_FORM_DATA)
+        self.assertEqual(0, self.bug.attachments.count())
+
+    def test_delete_action_private_bug_unautorized(self):
+        # Other users cannot delete private bug attachments.
+        user = self.factory.makePerson()
+        self.bug.setPrivate(True, self.bug_owner)
+        login_person(user)
+        self.assertRaises(
+            Unauthorized, create_initialized_view, self.bugattachment,
+            name='+edit', form=self.DELETE_FORM_DATA)

=== modified file 'lib/lp/bugs/doc/bugsubscription.txt'
=== modified file 'lib/lp/bugs/doc/initial-bug-contacts.txt'
--- lib/lp/bugs/doc/initial-bug-contacts.txt	2010-08-20 01:29:08 +0000
+++ lib/lp/bugs/doc/initial-bug-contacts.txt	2010-09-29 09:44:52 +0000
@@ -16,7 +16,7 @@
     >>> debian = getUtility(IDistributionSet).getByName("debian")
     >>> debian_firefox = debian.getSourcePackage("mozilla-firefox")
 
-    >>> debian_firefox.bug_subscriptions
+    >>> list(debian_firefox.bug_subscriptions)
     []
 
 Adding a package subscription is done with the

=== modified file 'lib/lp/bugs/model/bugmessage.py'
--- lib/lp/bugs/model/bugmessage.py	2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/model/bugmessage.py	2010-09-29 09:44:52 +0000
@@ -44,6 +44,8 @@
         notNull=False, default=None)
     remote_comment_id = StringCol(notNull=False, default=None)
     visible = BoolCol(notNull=True, default=True)
+    # -- Uncomment when deployed.
+    # index = IntCol()
 
 
 class BugMessageSet:

=== added file 'lib/lp/bugs/model/bugsubscriptionfilter.py'
--- lib/lp/bugs/model/bugsubscriptionfilter.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/bugsubscriptionfilter.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,36 @@
+# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+# pylint: disable-msg=E0611,W0212
+
+__metaclass__ = type
+__all__ = ['BugSubscriptionFilter']
+
+from storm.base import Storm
+from storm.locals import (
+    Bool,
+    Int,
+    Reference,
+    Unicode,
+    )
+
+
+class BugSubscriptionFilter(Storm):
+    """A filter to specialize a *structural* subscription."""
+
+    __storm_table__ = "BugSubscriptionFilter"
+
+    id = Int(primary=True)
+
+    structural_subscription_id = Int(
+        "structuralsubscription", allow_none=False)
+    structural_subscription = Reference(
+        structural_subscription_id, "StructuralSubscription.id")
+
+    find_all_tags = Bool(allow_none=False, default=False)
+    include_any_tags = Bool(allow_none=False, default=False)
+    exclude_any_tags = Bool(allow_none=False, default=False)
+
+    other_parameters = Unicode()
+
+    description = Unicode()

=== added file 'lib/lp/bugs/model/bugsubscriptionfilterimportance.py'
--- lib/lp/bugs/model/bugsubscriptionfilterimportance.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/bugsubscriptionfilterimportance.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,29 @@
+# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+# pylint: disable-msg=E0611,W0212
+
+__metaclass__ = type
+__all__ = ['BugSubscriptionFilterImportance']
+
+from storm.base import Storm
+from storm.locals import (
+    Int,
+    Reference,
+    )
+
+from canonical.database.enumcol import DBEnum
+from lp.bugs.interfaces.bugtask import BugTaskImportance
+
+
+class BugSubscriptionFilterImportance(Storm):
+    """Importances to filter."""
+
+    __storm_table__ = "BugSubscriptionFilterImportance"
+
+    id = Int(primary=True)
+
+    filter_id = Int("filter", allow_none=False)
+    filter = Reference(filter_id, "BugSubscriptionFilter.id")
+
+    importance = DBEnum(enum=BugTaskImportance, allow_none=False)

=== added file 'lib/lp/bugs/model/bugsubscriptionfilterstatus.py'
--- lib/lp/bugs/model/bugsubscriptionfilterstatus.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/bugsubscriptionfilterstatus.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,29 @@
+# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+# pylint: disable-msg=E0611,W0212
+
+__metaclass__ = type
+__all__ = ['BugSubscriptionFilterStatus']
+
+from storm.base import Storm
+from storm.locals import (
+    Int,
+    Reference,
+    )
+
+from canonical.database.enumcol import DBEnum
+from lp.bugs.interfaces.bugtask import BugTaskStatus
+
+
+class BugSubscriptionFilterStatus(Storm):
+    """Statuses to filter."""
+
+    __storm_table__ = "BugSubscriptionFilterStatus"
+
+    id = Int(primary=True)
+
+    filter_id = Int("filter", allow_none=False)
+    filter = Reference(filter_id, "BugSubscriptionFilter.id")
+
+    status = DBEnum(enum=BugTaskStatus, allow_none=False)

=== added file 'lib/lp/bugs/model/bugsubscriptionfiltertag.py'
--- lib/lp/bugs/model/bugsubscriptionfiltertag.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/bugsubscriptionfiltertag.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,29 @@
+# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+# pylint: disable-msg=E0611,W0212
+
+__metaclass__ = type
+__all__ = ['BugSubscriptionFilterTag']
+
+from storm.base import Storm
+from storm.locals import (
+    Bool,
+    Int,
+    Reference,
+    Unicode,
+    )
+
+
+class BugSubscriptionFilterTag(Storm):
+    """Tags to filter."""
+
+    __storm_table__ = "BugSubscriptionFilterTag"
+
+    id = Int(primary=True)
+
+    filter_id = Int("filter", allow_none=False)
+    filter = Reference(filter_id, "BugSubscriptionFilter.id")
+
+    include = Bool(allow_none=False)
+    tag = Unicode(allow_none=False)

=== added directory 'lib/lp/bugs/model/tests'
=== added file 'lib/lp/bugs/model/tests/__init__.py'
=== added file 'lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py'
--- lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/tests/test_bugsubscriptionfilter.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,66 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for the bugsubscription module."""
+
+__metaclass__ = type
+
+from canonical.launchpad.interfaces.lpstorm import IStore
+from canonical.testing import DatabaseFunctionalLayer
+from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
+from lp.testing import (
+    login_person,
+    TestCaseWithFactory,
+    )
+
+
+class TestBugSubscriptionFilter(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestBugSubscriptionFilter, self).setUp()
+        self.target = self.factory.makeProduct()
+        self.subscriber = self.target.owner
+        login_person(self.subscriber)
+        self.subscription = self.target.addBugSubscription(
+            self.subscriber, self.subscriber)
+
+    def test_basics(self):
+        """Test the basic operation of `BugSubscriptionFilter` objects."""
+        # Create.
+        bug_subscription_filter = BugSubscriptionFilter()
+        bug_subscription_filter.structural_subscription = self.subscription
+        bug_subscription_filter.find_all_tags = True
+        bug_subscription_filter.include_any_tags = True
+        bug_subscription_filter.exclude_any_tags = True
+        bug_subscription_filter.other_parameters = u"foo"
+        bug_subscription_filter.description = u"bar"
+        # Flush and reload.
+        IStore(bug_subscription_filter).flush()
+        IStore(bug_subscription_filter).reload(bug_subscription_filter)
+        # Check.
+        self.assertIsNot(None, bug_subscription_filter.id)
+        self.assertEqual(
+            self.subscription.id,
+            bug_subscription_filter.structural_subscription_id)
+        self.assertEqual(
+            self.subscription,
+            bug_subscription_filter.structural_subscription)
+        self.assertIs(True, bug_subscription_filter.find_all_tags)
+        self.assertIs(True, bug_subscription_filter.include_any_tags)
+        self.assertIs(True, bug_subscription_filter.exclude_any_tags)
+        self.assertEqual(u"foo", bug_subscription_filter.other_parameters)
+        self.assertEqual(u"bar", bug_subscription_filter.description)
+
+    def test_defaults(self):
+        """Test the default values of `BugSubscriptionFilter` objects."""
+        # Create.
+        bug_subscription_filter = BugSubscriptionFilter()
+        bug_subscription_filter.structural_subscription = self.subscription
+        # Check.
+        self.assertIs(False, bug_subscription_filter.find_all_tags)
+        self.assertIs(False, bug_subscription_filter.include_any_tags)
+        self.assertIs(False, bug_subscription_filter.exclude_any_tags)
+        self.assertIs(None, bug_subscription_filter.other_parameters)
+        self.assertIs(None, bug_subscription_filter.description)

=== added file 'lib/lp/bugs/model/tests/test_bugsubscriptionfilterimportance.py'
--- lib/lp/bugs/model/tests/test_bugsubscriptionfilterimportance.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/tests/test_bugsubscriptionfilterimportance.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,54 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for the bugsubscription module."""
+
+__metaclass__ = type
+
+from canonical.launchpad.interfaces.lpstorm import IStore
+from canonical.testing import DatabaseFunctionalLayer
+from lp.bugs.interfaces.bugtask import BugTaskImportance
+from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
+from lp.bugs.model.bugsubscriptionfilterimportance import (
+    BugSubscriptionFilterImportance,
+    )
+from lp.testing import (
+    login_person,
+    TestCaseWithFactory,
+    )
+
+
+class TestBugSubscriptionFilterImportance(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestBugSubscriptionFilterImportance, self).setUp()
+        self.target = self.factory.makeProduct()
+        self.subscriber = self.target.owner
+        login_person(self.subscriber)
+        self.subscription = self.target.addBugSubscription(
+            self.subscriber, self.subscriber)
+        self.subscription_filter = BugSubscriptionFilter()
+        self.subscription_filter.structural_subscription = self.subscription
+
+    def test_basics(self):
+        """Test the basics of `BugSubscriptionFilterImportance` objects."""
+        # Create.
+        bug_sub_filter_importance = BugSubscriptionFilterImportance()
+        bug_sub_filter_importance.filter = self.subscription_filter
+        bug_sub_filter_importance.importance = BugTaskImportance.HIGH
+        # Flush and reload.
+        IStore(bug_sub_filter_importance).flush()
+        IStore(bug_sub_filter_importance).reload(bug_sub_filter_importance)
+        # Check.
+        self.assertIsNot(None, bug_sub_filter_importance.id)
+        self.assertEqual(
+            self.subscription_filter.id,
+            bug_sub_filter_importance.filter_id)
+        self.assertEqual(
+            self.subscription_filter,
+            bug_sub_filter_importance.filter)
+        self.assertEqual(
+            BugTaskImportance.HIGH,
+            bug_sub_filter_importance.importance)

=== added file 'lib/lp/bugs/model/tests/test_bugsubscriptionfilterstatus.py'
--- lib/lp/bugs/model/tests/test_bugsubscriptionfilterstatus.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/tests/test_bugsubscriptionfilterstatus.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,52 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for the bugsubscription module."""
+
+__metaclass__ = type
+
+from canonical.launchpad.interfaces.lpstorm import IStore
+from canonical.testing import DatabaseFunctionalLayer
+from lp.bugs.interfaces.bugtask import BugTaskStatus
+from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
+from lp.bugs.model.bugsubscriptionfilterstatus import (
+    BugSubscriptionFilterStatus,
+    )
+from lp.testing import (
+    login_person,
+    TestCaseWithFactory,
+    )
+
+
+class TestBugSubscriptionFilterStatus(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestBugSubscriptionFilterStatus, self).setUp()
+        self.target = self.factory.makeProduct()
+        self.subscriber = self.target.owner
+        login_person(self.subscriber)
+        self.subscription = self.target.addBugSubscription(
+            self.subscriber, self.subscriber)
+        self.subscription_filter = BugSubscriptionFilter()
+        self.subscription_filter.structural_subscription = self.subscription
+
+    def test_basics(self):
+        """Test the basics of `BugSubscriptionFilterStatus` objects."""
+        # Create.
+        bug_sub_filter_status = BugSubscriptionFilterStatus()
+        bug_sub_filter_status.filter = self.subscription_filter
+        bug_sub_filter_status.status = BugTaskStatus.NEW
+        # Flush and reload.
+        IStore(bug_sub_filter_status).flush()
+        IStore(bug_sub_filter_status).reload(bug_sub_filter_status)
+        # Check.
+        self.assertIsNot(None, bug_sub_filter_status.id)
+        self.assertEqual(
+            self.subscription_filter.id,
+            bug_sub_filter_status.filter_id)
+        self.assertEqual(
+            self.subscription_filter,
+            bug_sub_filter_status.filter)
+        self.assertEqual(BugTaskStatus.NEW, bug_sub_filter_status.status)

=== added file 'lib/lp/bugs/model/tests/test_bugsubscriptionfiltertag.py'
--- lib/lp/bugs/model/tests/test_bugsubscriptionfiltertag.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/model/tests/test_bugsubscriptionfiltertag.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,51 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for the bugsubscription module."""
+
+__metaclass__ = type
+
+from canonical.launchpad.interfaces.lpstorm import IStore
+from canonical.testing import DatabaseFunctionalLayer
+from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
+from lp.bugs.model.bugsubscriptionfiltertag import BugSubscriptionFilterTag
+from lp.testing import (
+    login_person,
+    TestCaseWithFactory,
+    )
+
+
+class TestBugSubscriptionFilterTag(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestBugSubscriptionFilterTag, self).setUp()
+        self.target = self.factory.makeProduct()
+        self.subscriber = self.target.owner
+        login_person(self.subscriber)
+        self.subscription = self.target.addBugSubscription(
+            self.subscriber, self.subscriber)
+        self.subscription_filter = BugSubscriptionFilter()
+        self.subscription_filter.structural_subscription = self.subscription
+
+    def test_basics(self):
+        """Test the basics of `BugSubscriptionFilterTag` objects."""
+        # Create.
+        bug_sub_filter_tag = BugSubscriptionFilterTag()
+        bug_sub_filter_tag.filter = self.subscription_filter
+        bug_sub_filter_tag.include = True
+        bug_sub_filter_tag.tag = u"foo"
+        # Flush and reload.
+        IStore(bug_sub_filter_tag).flush()
+        IStore(bug_sub_filter_tag).reload(bug_sub_filter_tag)
+        # Check.
+        self.assertIsNot(None, bug_sub_filter_tag.id)
+        self.assertEqual(
+            self.subscription_filter.id,
+            bug_sub_filter_tag.filter_id)
+        self.assertEqual(
+            self.subscription_filter,
+            bug_sub_filter_tag.filter)
+        self.assertIs(True, bug_sub_filter_tag.include)
+        self.assertEqual(u"foo", bug_sub_filter_tag.tag)

=== modified file 'lib/lp/bugs/tests/has-bug-supervisor.txt'
--- lib/lp/bugs/tests/has-bug-supervisor.txt	2009-08-25 11:21:05 +0000
+++ lib/lp/bugs/tests/has-bug-supervisor.txt	2010-09-29 09:44:52 +0000
@@ -5,7 +5,7 @@
 structural subscription targets. When the bug supervisor for such an object
 is set, a new bug subscription is created as well.
 
-    >>> target.bug_subscriptions
+    >>> list(target.bug_subscriptions)
     []
 
     >>> print target.bug_supervisor

=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
--- lib/lp/buildmaster/interfaces/buildfarmjob.py	2010-09-16 12:27:46 +0000
+++ lib/lp/buildmaster/interfaces/buildfarmjob.py	2010-09-29 09:44:52 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 # pylint: disable-msg=E0211,E0213
@@ -256,6 +256,15 @@
 
     was_built = Attribute("Whether or not modified by the builddfarm.")
 
+    # This doesn't belong here.  It really belongs in IPackageBuild, but
+    # the TAL assumes it can read this directly.
+    dependencies = exported(
+        TextLine(
+            title=_('Dependencies'), required=False,
+            description=_(
+                'Debian-like dependency line that must be satisfied before '
+                'attempting to build this request.')))
+
 
 class ISpecificBuildFarmJob(IBuildFarmJob):
     """A marker interface with which to define adapters for IBuildFarmJob.

=== modified file 'lib/lp/buildmaster/interfaces/packagebuild.py'
--- lib/lp/buildmaster/interfaces/packagebuild.py	2010-09-10 12:29:36 +0000
+++ lib/lp/buildmaster/interfaces/packagebuild.py	2010-09-29 09:44:52 +0000
@@ -61,12 +61,6 @@
             description=_("A URL for failed upload logs."
                           "Will be None if there was no failure.")))
 
-    dependencies = exported(
-        TextLine(
-            title=_('Dependencies'), required=False,
-            description=_('Debian-like dependency line that must be satisfied'
-                          ' before attempting to build this request.')))
-
     build_farm_job = Reference(
         title=_('Build farm job'), schema=IBuildFarmJob, required=True,
         readonly=True, description=_('The base build farm job.'))

=== modified file 'lib/lp/buildmaster/model/buildfarmjob.py'
--- lib/lp/buildmaster/model/buildfarmjob.py	2010-09-16 12:27:46 +0000
+++ lib/lp/buildmaster/model/buildfarmjob.py	2010-09-29 09:44:52 +0000
@@ -237,6 +237,8 @@
 
     failure_count = Int(name='failure_count', allow_none=False)
 
+    dependencies = None
+
     def __init__(self, job_type, status=BuildStatus.NEEDSBUILD,
                  processor=None, virtualized=None, date_created=None):
         super(BuildFarmJob, self).__init__()
@@ -273,7 +275,7 @@
         return self.date_finished - self.date_started
 
     def makeJob(self):
-        """See `IBuildFarmJob`."""
+        """See `IBuildFarmJobOld`."""
         raise NotImplementedError
 
     def jobStarted(self):
@@ -420,7 +422,8 @@
             # specific private builds to which they have access.
             origin.extend(left_join_archive)
             origin.append(LeftJoin(
-                TeamParticipation, TeamParticipation.teamID == Archive.ownerID))
+                TeamParticipation,
+                TeamParticipation.teamID == Archive.ownerID))
             extra_clauses.append(
                 Or(Coalesce(Archive.private, False) == False,
                    TeamParticipation.person == user))

=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py	2010-08-24 10:45:57 +0000
+++ lib/lp/code/browser/branch.py	2010-09-29 09:44:52 +0000
@@ -104,6 +104,7 @@
 from canonical.widgets.branch import TargetBranchWidget
 from canonical.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
 from canonical.widgets.lazrjs import vocabulary_to_choice_edit_items
+from lp.app.errors import NotFoundError
 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
 from lp.bugs.interfaces.bug import IBugSet
 from lp.bugs.interfaces.bugbranch import IBugBranch
@@ -145,6 +146,9 @@
 from lp.registry.interfaces.productseries import IProductSeries
 from lp.registry.vocabularies import UserTeamsParticipationPlusSelfVocabulary
 from lp.services.propertycache import cachedproperty
+from lp.translations.interfaces.translationtemplatesbuild import (
+    ITranslationTemplatesBuildSource,
+    )
 
 
 def quote(text):
@@ -237,6 +241,16 @@
         """Traverses to the `ICodeImport` for the branch."""
         return self.context.code_import
 
+    @stepthrough("+translation-templates-build")
+    def traverse_translation_templates_build(self, id_string):
+        """Traverses to a `TranslationTemplatesBuild`."""
+        try:
+            buildfarmjob_id = int(id_string)
+        except ValueError:
+            raise NotFoundError(id_string)
+        source = getUtility(ITranslationTemplatesBuildSource)
+        return source.getByBuildFarmJob(buildfarmjob_id)
+
 
 class BranchEditMenu(NavigationMenu):
     """Edit menu for IBranch."""

=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
--- lib/lp/code/browser/branchmergeproposal.py	2010-09-27 20:47:58 +0000
+++ lib/lp/code/browser/branchmergeproposal.py	2010-09-29 09:44:52 +0000
@@ -547,7 +547,7 @@
         return diff_text.count('\n') >= config.diff.max_format_lines
 
 
-class ICodeReviewNewRevisions(Interface):
+class ICodeReviewNewRevisions(IComment):
     """Marker interface used to register views for CodeReviewNewRevisions."""
 
 
@@ -557,7 +557,7 @@
     Each object instance represents a number of revisions scanned at a
     particular time.
     """
-    implements(IComment, ICodeReviewNewRevisions)
+    implements(ICodeReviewNewRevisions)
 
     def __init__(self, revisions, date, branch):
         self.revisions = revisions
@@ -567,6 +567,13 @@
         # The date attribute is used to sort the comments in the conversation.
         self.date = date
 
+        # Other standard IComment attributes are not used.
+        self.extra_css_class = None
+        self.comment_author = None
+        self.body_text = None
+        self.comment_date = None
+        self.display_attachments = False
+
 
 class CodeReviewNewRevisionsView(LaunchpadView):
     """The view for rendering the new revisions."""

=== modified file 'lib/lp/code/browser/codereviewcomment.py'
--- lib/lp/code/browser/codereviewcomment.py	2010-08-24 10:45:57 +0000
+++ lib/lp/code/browser/codereviewcomment.py	2010-09-29 09:44:52 +0000
@@ -43,6 +43,10 @@
 from lp.services.propertycache import cachedproperty
 
 
+class ICodeReviewDisplayComment(IComment, ICodeReviewComment):
+    """Marker interface for displaying code review comments."""
+
+
 class CodeReviewDisplayComment:
     """A code review comment or activity or both.
 
@@ -51,7 +55,7 @@
     only code in the model itself.
     """
 
-    implements(IComment)
+    implements(ICodeReviewDisplayComment)
 
     delegates(ICodeReviewComment, 'comment')
 
@@ -70,6 +74,40 @@
         else:
             return ''
 
+    @cachedproperty
+    def comment_author(self):
+        """The author of the comment."""
+        return self.comment.message.owner
+
+    @cachedproperty
+    def has_body(self):
+        """Is there body text?"""
+        return bool(self.body_text)
+
+    @cachedproperty
+    def body_text(self):
+        """Get the body text for the message."""
+        return self.comment.message_body
+
+    @cachedproperty
+    def comment_date(self):
+        """The date of the comment."""
+        return self.comment.message.datecreated
+
+    @cachedproperty
+    def all_attachments(self):
+        return self.comment.getAttachments()
+
+    @cachedproperty
+    def display_attachments(self):
+        # Attachments to show.
+        return [DiffAttachment(alias) for alias in self.all_attachments[0]]
+
+    @cachedproperty
+    def other_attachments(self):
+        # Attachments to not show.
+        return self.all_attachments[1]
+
 
 class CodeReviewCommentPrimaryContext:
     """The primary context is the comment is that of the source branch."""
@@ -132,45 +170,11 @@
         """The decorated code review comment."""
         return CodeReviewDisplayComment(self.context)
 
-    @cachedproperty
-    def comment_author(self):
-        """The author of the comment."""
-        return self.context.message.owner
-
-    @cachedproperty
-    def has_body(self):
-        """Is there body text?"""
-        return bool(self.body_text)
-
-    @cachedproperty
-    def body_text(self):
-        """Get the body text for the message."""
-        return self.context.message_body
-
-    @cachedproperty
-    def comment_date(self):
-        """The date of the comment."""
-        return self.context.message.datecreated
-
     # Should the comment be shown in full?
     full_comment = True
     # Show comment expanders?
     show_expanders = False
 
-    @cachedproperty
-    def all_attachments(self):
-        return self.context.getAttachments()
-
-    @cachedproperty
-    def display_attachments(self):
-        # Attachments to show.
-        return [DiffAttachment(alias) for alias in self.all_attachments[0]]
-
-    @cachedproperty
-    def other_attachments(self):
-        # Attachments to not show.
-        return self.all_attachments[1]
-
 
 class CodeReviewCommentSummary(CodeReviewCommentView):
     """Summary view of a CodeReviewComment"""

=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml	2010-09-22 18:37:57 +0000
+++ lib/lp/code/browser/configure.zcml	2010-09-29 09:44:52 +0000
@@ -691,6 +691,16 @@
             name="+index"
             template="../templates/codereviewcomment-index.pt"/>
         <browser:page
+            name="+fragment"
+            template="../templates/codereviewcomment-fragment.pt"/>
+    </browser:pages>
+    <browser:pages
+        facet="branches"
+        for="lp.code.browser.codereviewcomment.ICodeReviewDisplayComment"
+        layer="lp.code.publisher.CodeLayer"
+        class="lp.code.browser.codereviewcomment.CodeReviewCommentView"
+        permission="zope.Public">
+        <browser:page
             name="+comment-header"
             template="../templates/codereviewcomment-header.pt"/>
         <browser:page
@@ -699,9 +709,6 @@
         <browser:page
             name="+comment-footer"
             template="../templates/codereviewcomment-footer.pt"/>
-        <browser:page
-            name="+fragment"
-            template="../templates/codereviewcomment-fragment.pt"/>
     </browser:pages>
     <browser:pages
         facet="branches"

=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
--- lib/lp/code/browser/tests/test_branchmergeproposal.py	2010-09-15 20:41:46 +0000
+++ lib/lp/code/browser/tests/test_branchmergeproposal.py	2010-09-29 09:44:52 +0000
@@ -23,6 +23,7 @@
 from canonical.launchpad.database.message import MessageSet
 from canonical.launchpad.webapp.interfaces import IPrimaryContext
 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+from canonical.launchpad.webapp.testing import verifyObject
 from canonical.testing import (
     DatabaseFunctionalLayer,
     LaunchpadFunctionalLayer,
@@ -35,8 +36,10 @@
     BranchMergeProposalMergedView,
     BranchMergeProposalVoteView,
     DecoratedCodeReviewVoteReference,
+    ICodeReviewNewRevisions,
     latest_proposals_for_each_branch,
     )
+from lp.code.browser.codereviewcomment import CodeReviewDisplayComment
 from lp.code.enums import (
     BranchMergeProposalStatus,
     CodeReviewVote,
@@ -45,6 +48,7 @@
     PreviewDiff,
     StaticDiff,
     )
+from lp.code.tests.helpers import add_revision_to_branch
 from lp.testing import (
     login_person,
     TestCaseWithFactory,
@@ -575,6 +579,20 @@
         view = create_initialized_view(self.bmp, '+index')
         self.assertEqual([], view.linked_bugs)
 
+    def test_CodeReviewNewRevisions_implements_ICodeReviewNewRevisions(self):
+        # The browser helper class implements its interface.
+        review_date = datetime(2009, 9, 10, tzinfo=pytz.UTC)
+        revision_date = review_date + timedelta(days=1)
+        bmp = self.factory.makeBranchMergeProposal(
+            date_created=review_date)
+        revision = add_revision_to_branch(
+            self.factory, bmp.source_branch, revision_date)
+
+        view = create_initialized_view(bmp, '+index')
+        new_revisions = view.conversation.comments[0]
+
+        self.assertTrue(verifyObject(ICodeReviewNewRevisions, new_revisions))
+
     def test_include_superseded_comments(self):
         for x, time in zip(range(3), time_counter()):
             if x != 0:
@@ -707,7 +725,8 @@
             body='testing',
             attachments=[('test.diff', 'text/plain', attachment_body)])
         message = MessageSet().fromEmail(msg.as_string())
-        return bmp.createCommentFromMessage(message, None, None, msg)
+        return CodeReviewDisplayComment(
+            bmp.createCommentFromMessage(message, None, None, msg))
 
     def test_nonascii_in_attachment_renders(self):
         # The view should render without errors.
@@ -723,7 +742,7 @@
         # Need to commit in order to read the diff out of the librarian.
         transaction.commit()
         view = create_initialized_view(comment, '+comment-body')
-        [diff_attachment] = view.display_attachments
+        [diff_attachment] = view.comment.display_attachments
         self.assertEqual(u'\u2615', diff_attachment.diff_text)
 
 

=== modified file 'lib/lp/code/browser/tests/test_codereviewcomment.py'
--- lib/lp/code/browser/tests/test_codereviewcomment.py	2010-08-20 20:31:18 +0000
+++ lib/lp/code/browser/tests/test_codereviewcomment.py	2010-09-29 09:44:52 +0000
@@ -3,35 +3,52 @@
 
 """Unit tests for CodeReviewComments."""
 
+from __future__ import with_statement
+
 __metaclass__ = type
 
 import unittest
 
 from canonical.launchpad.webapp.interfaces import IPrimaryContext
+from canonical.launchpad.webapp.testing import verifyObject
 from canonical.testing import DatabaseFunctionalLayer
+from lp.code.browser.codereviewcomment import (
+    CodeReviewDisplayComment,
+    ICodeReviewDisplayComment,
+    )
 from lp.testing import (
-    login_person,
+    person_logged_in,
     TestCaseWithFactory,
     )
 
 
-class TestCodeReviewCommentPrimaryContext(TestCaseWithFactory):
-    # Tests the adaptation of a code review comment into a primary context.
+class TestCodeReviewComments(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer
 
     def testPrimaryContext(self):
+        # Tests the adaptation of a code review comment into a primary
+        # context.
         # We need a person to make a comment.
-        commenter = self.factory.makePerson()
-        login_person(commenter)
-        # The primary context of a code review comment is the same as the
-        # primary context for the branch merge proposal that the comment is
-        # for.
-        comment = self.factory.makeCodeReviewComment()
+        with person_logged_in(self.factory.makePerson()):
+            # The primary context of a code review comment is the same
+            # as the primary context for the branch merge proposal that
+            # the comment is for.
+            comment = self.factory.makeCodeReviewComment()
+
         self.assertEqual(
             IPrimaryContext(comment).context,
             IPrimaryContext(comment.branch_merge_proposal).context)
 
+    def test_display_comment_provides_icodereviewdisplaycomment(self):
+        # The CodeReviewDisplayComment class provides IComment.
+        with person_logged_in(self.factory.makePerson()):
+            comment = self.factory.makeCodeReviewComment()
+
+        display_comment = CodeReviewDisplayComment(comment)
+
+        verifyObject(ICodeReviewDisplayComment, display_comment)
+
 
 def test_suite():
     return unittest.TestLoader().loadTestsFromName(__name__)

=== modified file 'lib/lp/code/doc/branch.txt'
--- lib/lp/code/doc/branch.txt	2010-07-26 09:15:31 +0000
+++ lib/lp/code/doc/branch.txt	2010-09-29 09:44:52 +0000
@@ -379,6 +379,7 @@
     sourcepackagerecipedata.base_branch
     sourcepackagerecipedatainstruction.branch
     specificationbranch.branch
+    translationtemplatesbuild.branch
 
 (Unfortunately, references can form a cycle-- note that codereviewcomments
  aren't shown.)

=== modified file 'lib/lp/code/interfaces/branchrevision.py'
--- lib/lp/code/interfaces/branchrevision.py	2010-09-09 01:04:26 +0000
+++ lib/lp/code/interfaces/branchrevision.py	2010-09-29 09:44:52 +0000
@@ -26,8 +26,6 @@
     ancestry of a branch. History revisions have an integer sequence, merged
     revisions have sequence set to None.
     """
-    id = Int(title=_('The database revision ID'))
-
     sequence = Int(
         title=_("Revision number"), required=True,
         description=_("The index of the revision within the branch's history."

=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py	2010-09-21 02:46:02 +0000
+++ lib/lp/code/model/branch.py	2010-09-29 09:44:52 +0000
@@ -919,7 +919,7 @@
     def getScannerData(self):
         """See `IBranch`."""
         columns = (
-            BranchRevision.id, BranchRevision.sequence, Revision.revision_id)
+            BranchRevision.sequence, Revision.revision_id)
         rows = Store.of(self).using(Revision, BranchRevision).find(
             columns,
             Revision.id == BranchRevision.revision_id,
@@ -927,7 +927,7 @@
         rows = rows.order_by(BranchRevision.sequence)
         ancestry = set()
         history = []
-        for branch_revision_id, sequence, revision_id in rows:
+        for sequence, revision_id in rows:
             ancestry.add(revision_id)
             if sequence is not None:
                 history.append(revision_id)
@@ -1027,10 +1027,14 @@
         """Delete jobs for this branch prior to deleting branch.
 
         This deletion includes `BranchJob`s associated with the branch,
-        as well as `BuildQueue` entries for `TranslationTemplateBuildJob`s.
+        as well as `BuildQueue` entries for `TranslationTemplateBuildJob`s
+        and `TranslationTemplateBuild`s.
         """
         # Avoid circular imports.
         from lp.code.model.branchjob import BranchJob
+        from lp.translations.model.translationtemplatesbuild import (
+            TranslationTemplatesBuild,
+            )
 
         store = Store.of(self)
         affected_jobs = Select(
@@ -1044,6 +1048,10 @@
         # Delete Jobs.  Their BranchJobs cascade along in the database.
         store.find(Job, Job.id.is_in(affected_jobs)).remove()
 
+        store.find(
+            TranslationTemplatesBuild,
+            TranslationTemplatesBuild.branch == self).remove()
+
     def destroySelf(self, break_references=False):
         """See `IBranch`."""
         from lp.code.interfaces.branchjob import IReclaimBranchSpaceJobSource

=== modified file 'lib/lp/code/model/branchmergeproposal.py'
--- lib/lp/code/model/branchmergeproposal.py	2010-09-22 18:55:25 +0000
+++ lib/lp/code/model/branchmergeproposal.py	2010-09-29 09:44:52 +0000
@@ -633,7 +633,7 @@
             SourceRevision,
             SourceRevision.branch_id == self.source_branch.id,
             SourceRevision.sequence != None,
-            TargetRevision.id == None)
+            TargetRevision.branch_id == None)
         return result.order_by(Desc(SourceRevision.sequence)).config(limit=10)
 
     def createComment(self, owner, subject, content=None, vote=None,

=== modified file 'lib/lp/code/model/branchrevision.py'
--- lib/lp/code/model/branchrevision.py	2010-09-09 01:04:26 +0000
+++ lib/lp/code/model/branchrevision.py	2010-09-29 09:44:52 +0000
@@ -21,8 +21,7 @@
 class BranchRevision(Storm):
     """See `IBranchRevision`."""
     __storm_table__ = 'BranchRevision'
-
-    id = Int(primary=True)
+    __storm_primary__ = "branch_id", "revision_id"
 
     implements(IBranchRevision)
 

=== modified file 'lib/lp/code/model/tests/test_branchjob.py'
--- lib/lp/code/model/tests/test_branchjob.py	2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/tests/test_branchjob.py	2010-09-29 09:44:52 +0000
@@ -518,9 +518,7 @@
             except bzr_errors.NoSuchRevision:
                 revno = None
             if existing is not None:
-                branchrevision = IMasterStore(branch).find(
-                    BranchRevision, BranchRevision.id == existing.id)
-                branchrevision.remove()
+                branch.removeBranchRevisions([existing.revision.revision_id])
             branch.createBranchRevision(revno, revision)
 
     def create3CommitsBranch(self):

=== modified file 'lib/lp/code/templates/codereviewcomment-body.pt'
--- lib/lp/code/templates/codereviewcomment-body.pt	2010-04-16 04:20:43 +0000
+++ lib/lp/code/templates/codereviewcomment-body.pt	2010-09-29 09:44:52 +0000
@@ -3,9 +3,9 @@
    xmlns:metal="http://xml.zope.org/namespaces/metal";
    omit-tag="">
 
-  <tal:message replace="structure view/body_text/fmt:obfuscate-email/fmt:nice_pre" />
+  <tal:message replace="structure view/comment/body_text/fmt:obfuscate-email/fmt:nice_pre" />
 
-  <tal:good-attachments repeat="attachment view/display_attachments">
+  <tal:good-attachments repeat="attachment view/comment/display_attachments">
     <div class="boardComment attachment">
       <div class="boardCommentDetails filename"><a tal:content="attachment/filename" tal:attributes="href attachment/getURL"/></div>
       <div class="boardCommentBody attachmentBody" tal:content="structure attachment/diff_text/fmt:diff"/>

=== modified file 'lib/lp/code/templates/codereviewcomment-header.pt'
--- lib/lp/code/templates/codereviewcomment-header.pt	2010-02-18 16:40:09 +0000
+++ lib/lp/code/templates/codereviewcomment-header.pt	2010-09-29 09:44:52 +0000
@@ -3,9 +3,9 @@
    xmlns:metal="http://xml.zope.org/namespaces/metal";
    omit-tag="">
 
-  <tal:author replace="structure view/comment_author/fmt:link:mainsite"/>
-  <tal:has-body condition="view/has_body">wrote</tal:has-body>
-  <tal:date replace="view/comment_date/fmt:displaydate" />
+  <tal:author replace="structure context/comment_author/fmt:link:mainsite"/>
+  <tal:has-body condition="context/has_body">wrote</tal:has-body>
+  <tal:date replace="context/comment_date/fmt:displaydate" />
   <span tal:condition="context/from_superseded" class="sprite warning-icon"
         style="float: right">Posted in <a
           tal:attributes="href context/branch_merge_proposal/fmt:url">a

=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml	2010-09-29 05:14:26 +0000
+++ lib/lp/registry/browser/configure.zcml	2010-09-29 09:44:52 +0000
@@ -166,6 +166,28 @@
         class="lp.registry.browser.distroseries.DistroSeriesLocalDifferences"
         template="../templates/distroseries-localdifferences.pt"
         permission="zope.Public"/>
+    <browser:url
+        for="lp.registry.interfaces.distroseriesdifference.IDistroSeriesDifference"
+        path_expression="string:+difference/${source_package_name/name}"
+        rootsite="mainsite"
+        attribute_to_parent="derived_series"/>
+    <browser:page
+        name="+listing-distroseries-extra"
+        for="lp.registry.interfaces.distroseriesdifference.IDistroSeriesDifference"
+        class="lp.registry.browser.distroseriesdifference.DistroSeriesDifferenceView"
+        template="../templates/distroseriesdifference-listing-extra.pt"
+        permission="zope.Public"/>
+    <browser:defaultView
+        for="lp.registry.interfaces.distroseriesdifference.IDistroSeriesDifference"
+        name="+listing-distroseries-extra"/>
+    <browser:navigation
+        module="lp.registry.browser.distroseriesdifference"
+        classes="DistroSeriesDifferenceNavigation"/>
+    <browser:url
+        for="lp.registry.interfaces.distroseriesdifferencecomment.IDistroSeriesDifferenceComment"
+        path_expression="string:comments/${id}"
+        attribute_to_parent="distro_series_difference"
+        rootsite="mainsite"/>
     <browser:menus
         classes="
             DistroSeriesFacets

=== modified file 'lib/lp/registry/browser/distroseries.py'
--- lib/lp/registry/browser/distroseries.py	2010-09-08 07:53:06 +0000
+++ lib/lp/registry/browser/distroseries.py	2010-09-29 09:44:52 +0000
@@ -74,7 +74,7 @@
 from lp.registry.interfaces.distroseriesdifference import (
     IDistroSeriesDifferenceSource)
 from lp.registry.interfaces.series import SeriesStatus
-from lp.services.features.flags import FeatureController
+from lp.services.features import getFeatureFlag
 from lp.services.propertycache import cachedproperty
 from lp.services.worlddata.interfaces.country import ICountry
 from lp.services.worlddata.interfaces.language import ILanguageSet
@@ -147,6 +147,11 @@
     def traverse_queue(self, id):
         return getUtility(IPackageUploadSet).get(id)
 
+    @stepthrough('+difference')
+    def traverse_difference(self, name):
+        dsd_source = getUtility(IDistroSeriesDifferenceSource)
+        return dsd_source.getByDistroSeriesAndName(self.context, name)
+
 
 class DistroSeriesBreadcrumb(Breadcrumb):
     """Builds a breadcrumb for an `IDistroSeries`."""
@@ -538,11 +543,7 @@
 
     def initialize(self):
         """Redirect to the derived series if the feature is not enabled."""
-        def in_scope(value):
-            return True
-
-        feature_controller = FeatureController(in_scope)
-        if feature_controller.getFlag('soyuz.derived-series-ui.enabled') != 'on':
+        if not getFeatureFlag('soyuz.derived-series-ui.enabled'):
             self.request.response.redirect(canonical_url(self.context))
             return
         super(DistroSeriesLocalDifferences, self).initialize()
@@ -559,6 +560,6 @@
     @cachedproperty
     def cached_differences(self):
         """Return a batch navigator of potentially filtered results."""
-        differences = getUtility(IDistroSeriesDifferenceSource).getForDistroSeries(
-            self.context)
+        utility = getUtility(IDistroSeriesDifferenceSource)
+        differences = utility.getForDistroSeries(self.context)
         return BatchNavigator(differences, self.request)

=== added file 'lib/lp/registry/browser/distroseriesdifference.py'
--- lib/lp/registry/browser/distroseriesdifference.py	1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/distroseriesdifference.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,134 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Browser views for DistroSeriesDifferences."""
+
+__metaclass__ = type
+__all__ = [
+    'DistroSeriesDifferenceView',
+    ]
+
+from zope.app.form.browser.itemswidgets import RadioWidget
+from zope.component import getUtility
+from zope.interface import (
+    implements,
+    Interface,
+    )
+from zope.schema import Choice
+from zope.schema.vocabulary import (
+    SimpleTerm,
+    SimpleVocabulary,
+    )
+
+from canonical.launchpad.webapp import (
+    LaunchpadFormView,
+    Navigation,
+    stepthrough,
+    )
+from canonical.launchpad.webapp.authorization import check_permission
+from canonical.launchpad.webapp.launchpadform import custom_widget
+from lp.registry.enum import DistroSeriesDifferenceStatus
+from lp.registry.interfaces.distroseriesdifference import (
+    IDistroSeriesDifference,
+    )
+from lp.registry.interfaces.distroseriesdifferencecomment import (
+    IDistroSeriesDifferenceCommentSource,
+    )
+from lp.services.comments.interfaces.conversation import (
+    IComment,
+    IConversation,
+    )
+
+
+class DistroSeriesDifferenceNavigation(Navigation):
+    usedfor = IDistroSeriesDifference
+
+    @stepthrough('comments')
+    def traverse_comment(self, id_str):
+        try:
+            id = int(id_str)
+        except ValueError:
+            return None
+
+        return getUtility(
+            IDistroSeriesDifferenceCommentSource).getForDifference(
+                self.context, id)
+
+
+class IDistroSeriesDifferenceForm(Interface):
+    """An interface used in the browser only for displaying form elements."""
+    blacklist_options = Choice(vocabulary=SimpleVocabulary((
+        SimpleTerm('NONE', 'NONE', 'No'),
+        SimpleTerm(
+            DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
+            DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS.name,
+            'All versions'),
+        SimpleTerm(
+            DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
+            DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT.name,
+            'This version'),
+        )))
+
+
+class DistroSeriesDifferenceView(LaunchpadFormView):
+
+    implements(IConversation)
+    schema = IDistroSeriesDifferenceForm
+    custom_widget('blacklist_options', RadioWidget)
+
+    @property
+    def initial_values(self):
+        """Ensure the correct radio button is checked for blacklisting."""
+        blacklisted_statuses = (
+            DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
+            DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
+            )
+        if self.context.status in blacklisted_statuses:
+            return dict(blacklist_options=self.context.status)
+
+        return dict(blacklist_options='NONE')
+
+    @property
+    def binary_summaries(self):
+        """Return the summary of the related binary packages."""
+        source_pub = None
+        if self.context.source_pub is not None:
+            source_pub = self.context.source_pub
+        elif self.context.parent_source_pub is not None:
+            source_pub = self.context.parent_source_pub
+
+        if source_pub is not None:
+            summary = source_pub.meta_sourcepackage.summary
+            if summary:
+                return summary.split('\n')
+
+        return None
+
+    @property
+    def comments(self):
+        """See `IConversation`."""
+        return [
+            DistroSeriesDifferenceDisplayComment(comment) for
+                comment in self.context.getComments()]
+
+    @property
+    def show_edit_options(self):
+        """Only show the options if an editor requests via JS."""
+        return self.request.is_ajax and check_permission(
+            'launchpad.Edit', self.context)
+
+
+class DistroSeriesDifferenceDisplayComment:
+    """Used simply to provide `IComment` for rendering."""
+    implements(IComment)
+
+    has_body = True
+    has_footer = False
+    display_attachments = False
+    extra_css_class = ''
+
+    def __init__(self, comment):
+        """Setup the attributes required by `IComment`."""
+        self.comment_author = comment.comment_author
+        self.comment_date = comment.comment_date
+        self.body_text = comment.body_text

=== added file 'lib/lp/registry/browser/tests/test_distroseriesdifference_views.py'
--- lib/lp/registry/browser/tests/test_distroseriesdifference_views.py	1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/test_distroseriesdifference_views.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,293 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Unit tests for the DistroSeriesDifference views."""
+
+from __future__ import with_statement
+
+__metaclass__ = type
+
+from BeautifulSoup import BeautifulSoup
+from zope.component import getUtility
+
+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+from canonical.launchpad.webapp.testing import verifyObject
+from canonical.testing import (
+    DatabaseFunctionalLayer,
+    LaunchpadFunctionalLayer,
+    )
+from lp.registry.browser.distroseriesdifference import (
+    DistroSeriesDifferenceDisplayComment,
+    )
+from lp.registry.enum import (
+    DistroSeriesDifferenceStatus,
+    DistroSeriesDifferenceType,
+    )
+from lp.registry.interfaces.distroseriesdifference import (
+    IDistroSeriesDifferenceSource)
+from lp.services.comments.interfaces.conversation import (
+    IComment,
+    IConversation,
+    )
+from lp.soyuz.enums import PackagePublishingStatus
+from lp.testing import (
+    celebrity_logged_in,
+    person_logged_in,
+    TestCaseWithFactory,
+    )
+from lp.testing.views import create_initialized_view
+
+
+class DistroSeriesDifferenceTestCase(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_provides_conversation(self):
+        # The DSDView provides a conversation implementation.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+        self.assertTrue(verifyObject(IConversation, view))
+
+    def test_comment_for_display_provides_icomment(self):
+        # The DSDDisplayComment browser object provides IComment.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+        owner = ds_diff.derived_series.owner
+        with person_logged_in(owner):
+            comment = ds_diff.addComment(owner, "I'm working on this.")
+        comment_for_display = DistroSeriesDifferenceDisplayComment(comment)
+
+        self.assertTrue(verifyObject(IComment, comment_for_display))
+
+    def addSummaryToDifference(self, distro_series_difference):
+        """Helper that adds binaries with summary info to the source pubs."""
+        # Avoid circular import.
+        from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
+        distro_series = distro_series_difference.derived_series
+        source_package_name_str = (
+            distro_series_difference.source_package_name.name)
+        stp = SoyuzTestPublisher()
+
+        if distro_series_difference.difference_type == (
+            DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES):
+            source_pub = distro_series_difference.parent_source_pub
+        else:
+            source_pub = distro_series_difference.source_pub
+
+        stp.makeSourcePackageSummaryData(source_pub)
+        stp.updateDistroSeriesPackageCache(source_pub.distroseries)
+
+        # updateDistroSeriesPackageCache reconnects the db, so the
+        # objects need to be reloaded.
+        dsd_source = getUtility(IDistroSeriesDifferenceSource)
+        ds_diff = dsd_source.getByDistroSeriesAndName(
+            distro_series, source_package_name_str)
+        return ds_diff
+
+    def test_binary_summaries_for_source_pub(self):
+        # For packages unique to the derived series (or different
+        # versions) the summary is based on the derived source pub.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+        ds_diff = self.addSummaryToDifference(ds_diff)
+
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+
+        self.assertIsNot(None, view.binary_summaries)
+        self.assertEqual([
+            u'flubber-bin: summary for flubber-bin',
+            u'flubber-lib: summary for flubber-lib',
+            ], view.binary_summaries)
+
+    def test_binary_summaries_for_missing_difference(self):
+        # For packages only in the parent series, the summary is based
+        # on the parent publication.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            difference_type=(
+                DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES))
+        ds_diff = self.addSummaryToDifference(ds_diff)
+
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+
+        self.assertIsNot(None, view.binary_summaries)
+        self.assertEqual([
+            u'flubber-bin: summary for flubber-bin',
+            u'flubber-lib: summary for flubber-lib',
+            ], view.binary_summaries)
+
+    def test_binary_summaries_no_pubs(self):
+        # If the difference has been resolved by removing packages then
+        # there will not be a summary.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            difference_type=(
+                DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES))
+        with celebrity_logged_in('admin'):
+            ds_diff.parent_source_pub.status = PackagePublishingStatus.DELETED
+        ds_diff.update()
+
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+
+        self.assertIs(None, ds_diff.parent_source_pub)
+        self.assertIs(None, ds_diff.source_pub)
+        self.assertIs(None, view.binary_summaries)
+
+    def test_show_edit_options_non_ajax(self):
+        # Blacklist options are not shown for non-ajax requests.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+
+        # Without JS, even editors don't see blacklist options.
+        with person_logged_in(ds_diff.owner):
+            view = create_initialized_view(
+                ds_diff, '+listing-distroseries-extra')
+        self.assertFalse(view.show_edit_options)
+
+    def test_show_edit_options_editor(self):
+        # Blacklist options are shown if requested by an editor via
+        # ajax.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+
+        request = LaunchpadTestRequest(HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+        with person_logged_in(ds_diff.owner):
+            view = create_initialized_view(
+                ds_diff, '+listing-distroseries-extra', request=request)
+            self.assertTrue(view.show_edit_options)
+
+    def test_show_edit_options_non_editor(self):
+        # Even with a JS request, non-editors do not see the options.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+
+        request = LaunchpadTestRequest(HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+        view = create_initialized_view(
+            ds_diff, '+listing-distroseries-extra', request=request)
+        self.assertFalse(view.show_edit_options)
+
+
+class DistroSeriesDifferenceTemplateTestCase(TestCaseWithFactory):
+
+    layer = LaunchpadFunctionalLayer
+
+    def number_of_request_diff_texts(self, html):
+        """Check that the html doesn't include the request diff text."""
+        soup = BeautifulSoup(html)
+        return len(soup.findAll('li', 'request-derived-diff'))
+
+    def contains_one_link_to_diff(self, html, package_diff):
+        """Return whether the html contains a link to the diff content."""
+        soup = BeautifulSoup(html)
+        return 1 == len(soup.findAll(
+            'a', href=package_diff.diff_content.http_url))
+
+    def test_both_request_diff_texts_rendered(self):
+        # An unlinked description of a potential diff is displayed when
+        # no diff is present.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+        # Both diffs present simple text repr. of proposed diff.
+        self.assertEqual(2, self.number_of_request_diff_texts(view()))
+
+    def test_source_diff_rendering_diff(self):
+        # A linked description of the diff is displayed when
+        # it is present.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+
+        with person_logged_in(ds_diff.derived_series.owner):
+            ds_diff.package_diff = self.factory.makePackageDiff()
+
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+        # The text for the parent diff remains, but the source package
+        # diff is now a link.
+        self.assertEqual(1, self.number_of_request_diff_texts(view()))
+        self.assertTrue(
+            self.contains_one_link_to_diff(view(), ds_diff.package_diff))
+
+    def test_source_diff_rendering_no_source(self):
+        # If there is no source pub for this difference, then we don't
+        # display even the request for a diff.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            difference_type=
+                (DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES))
+
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+        self.assertEqual(1, self.number_of_request_diff_texts(view()))
+
+    def test_parent_source_diff_rendering_diff(self):
+        # A linked description of the diff is displayed when
+        # it is present.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+
+        with person_logged_in(ds_diff.derived_series.owner):
+            ds_diff.parent_package_diff = self.factory.makePackageDiff()
+
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+        # The text for the source diff remains, but the parent package
+        # diff is now a link.
+        self.assertEqual(1, self.number_of_request_diff_texts(view()))
+        self.assertTrue(
+            self.contains_one_link_to_diff(
+                view(), ds_diff.parent_package_diff))
+
+    def test_parent_source_diff_rendering_no_source(self):
+        # If there is no source pub for this difference, then we don't
+        # display even the request for a diff.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            difference_type=
+                (DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES))
+
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+        self.assertEqual(1, self.number_of_request_diff_texts(view()))
+
+    def test_comments_rendered(self):
+        # If there are comments on the difference, they are rendered.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+        owner = ds_diff.derived_series.owner
+        with person_logged_in(owner):
+            ds_diff.addComment(owner, "I'm working on this.")
+            ds_diff.addComment(owner, "Here's another comment.")
+
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+        soup = BeautifulSoup(view())
+
+        self.assertEqual(
+            1, len(soup.findAll('pre', text="I'm working on this.")))
+        self.assertEqual(
+            1, len(soup.findAll('pre', text="Here's another comment.")))
+
+    def test_blacklist_options(self):
+        # blacklist options are presented to editors.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+
+        with person_logged_in(ds_diff.owner):
+            request = LaunchpadTestRequest(
+                HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+            view = create_initialized_view(
+                ds_diff, '+listing-distroseries-extra', request=request)
+            soup = BeautifulSoup(view())
+
+        self.assertEqual(
+            1, len(soup.findAll('div', {'class': 'blacklist-options'})))
+
+    def test_blacklist_options_initial_values_none(self):
+        ds_diff = self.factory.makeDistroSeriesDifference()
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+
+        # If the difference is not currently blacklisted, 'NONE' is set
+        # as the default value for the field.
+        self.assertEqual('NONE', view.initial_values.get('blacklist_options'))
+
+    def test_blacklist_options_initial_values_current(self):
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
+            view.initial_values.get('blacklist_options'))
+
+    def test_blacklist_options_initial_values_always(self):
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS)
+        view = create_initialized_view(ds_diff, '+listing-distroseries-extra')
+
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
+            view.initial_values.get('blacklist_options'))

=== added file 'lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py'
--- lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py	1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,101 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+from __future__ import with_statement
+
+__metaclass__ = type
+
+import transaction
+
+from lazr.restfulclient.errors import Unauthorized
+from storm.store import Store
+from zope.component import getUtility
+
+from canonical.testing import AppServerLayer
+from canonical.launchpad.webapp.publisher import canonical_url
+from lp.registry.enum import DistroSeriesDifferenceStatus
+from lp.registry.interfaces.distroseriesdifference import (
+    IDistroSeriesDifferenceSource,
+    )
+from lp.testing import (
+    person_logged_in,
+    TestCaseWithFactory,
+    ws_object,
+    )
+
+
+class DistroSeriesDifferenceWebServiceTestCase(TestCaseWithFactory):
+
+    layer = AppServerLayer
+
+    def test_get_difference(self):
+        # DistroSeriesDifferences are available on the web service.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+        ds_diff_path = canonical_url(ds_diff).replace(
+            'http://launchpad.dev', '')
+
+        ws_diff = ws_object(self.factory.makeLaunchpadService(), ds_diff)
+
+        self.assertTrue(
+            ws_diff.self_link.endswith(ds_diff_path))
+
+    def test_blacklist_not_public(self):
+        # The blacklist method is not publically available.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+        ws_diff = ws_object(self.factory.makeLaunchpadService(), ds_diff)
+
+        self.assertRaises(Unauthorized, ws_diff.blacklist)
+
+    def test_blacklist(self):
+        # The blacklist method can be called by people with edit access.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+        ws_diff = ws_object(self.factory.makeLaunchpadService(
+            ds_diff.derived_series.owner), ds_diff)
+
+        result = ws_diff.blacklist()
+        transaction.commit()
+
+        utility = getUtility(IDistroSeriesDifferenceSource)
+        ds_diff = utility.getByDistroSeriesAndName(
+            ds_diff.derived_series, ds_diff.source_package_name.name)
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
+            ds_diff.status)
+
+    def test_unblacklist_not_public(self):
+        # The unblacklist method is not publically available.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
+        ws_diff = ws_object(self.factory.makeLaunchpadService(), ds_diff)
+
+        self.assertRaises(Unauthorized, ws_diff.unblacklist)
+
+    def test_unblacklist(self):
+        # The unblacklist method can be called by people with edit access.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
+        ws_diff = ws_object(self.factory.makeLaunchpadService(
+            ds_diff.owner), ds_diff)
+
+        result = ws_diff.unblacklist()
+        transaction.commit()
+
+        utility = getUtility(IDistroSeriesDifferenceSource)
+        ds_diff = utility.getByDistroSeriesAndName(
+            ds_diff.derived_series, ds_diff.source_package_name.name)
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
+            ds_diff.status)
+
+    def test_addComment(self):
+        # Comments can be added via the API
+        ds_diff = self.factory.makeDistroSeriesDifference()
+        ws_diff = ws_object(self.factory.makeLaunchpadService(
+            ds_diff.owner), ds_diff)
+
+        result = ws_diff.addComment(comment='Hey there')
+
+        self.assertEqual('Hey there', result['body_text'])
+        self.assertTrue(
+            result['resource_type_link'].endswith(
+                '#distro_series_difference_comment'))

=== modified file 'lib/lp/registry/browser/tests/test_series_views.py'
--- lib/lp/registry/browser/tests/test_series_views.py	2010-09-08 07:53:06 +0000
+++ lib/lp/registry/browser/tests/test_series_views.py	2010-09-29 09:44:52 +0000
@@ -20,7 +20,14 @@
     DistroSeriesDifferenceType,
     )
 from lp.services.features.flags import FeatureController
-from lp.services.features.model import FeatureFlag, getFeatureStore
+from lp.services.features.model import (
+    FeatureFlag,
+    getFeatureStore,
+    )
+from lp.services.features import (
+    getFeatureFlag,
+    per_thread,
+    )
 from lp.testing import TestCaseWithFactory
 from lp.testing.views import create_initialized_view
 
@@ -72,13 +79,16 @@
             scope=u'default', flag=u'soyuz.derived-series-ui.enabled',
             value=u'on', priority=1))
 
-    def getDerivedSeriesUIFeatureFlag(self, flag):
-        """Helper to return the given flag leaving tests more readable."""
+        # XXX Michael Nelson 2010-09-21 bug=631884
+        # Currently LaunchpadTestRequest doesn't set per-thread
+        # features.
         def in_scope(value):
             return True
+        per_thread.features = FeatureController(in_scope)
 
-        feature_controller = FeatureController(in_scope)
-        return feature_controller.getFlag(flag)
+        def reset_per_thread_features():
+            per_thread.features = None
+        self.addCleanup(reset_per_thread_features)
 
     def test_view_redirects_without_feature_flag(self):
         # If the feature flag soyuz.derived-series-ui.enabled is not set the
@@ -87,8 +97,7 @@
             parent_name='lucid', derived_name='derilucid')
 
         self.assertIs(
-            None, self.getDerivedSeriesUIFeatureFlag(
-                'soyuz.derived-series-ui.enabled'))
+            None, getFeatureFlag('soyuz.derived-series-ui.enabled'))
         view = create_initialized_view(
             derived_series, '+localpackagediffs')
 
@@ -181,6 +190,25 @@
         self.assertIn("Latest comment", unicode(rows[0]))
         self.assertNotIn("Earlier comment", unicode(rows[0]))
 
+    def test_diff_row_links_to_extra_details(self):
+        # The source package name links to the difference details.
+        derived_series = self.makeDerivedSeries(
+            parent_name='lucid', derived_name='derilucid')
+        difference = self.factory.makeDistroSeriesDifference(
+            derived_series=derived_series)
+
+        self.setDerivedSeriesUIFeatureFlag()
+        view = create_initialized_view(
+            derived_series, '+localpackagediffs')
+        soup = BeautifulSoup(view())
+        diff_table = soup.find('table', {'class': 'listing'})
+        row = diff_table.tbody.findAll('tr')[0]
+
+        href = canonical_url(difference).replace('http://launchpad.dev', '')
+        links = row.findAll('a', href=href)
+        self.assertEqual(1, len(links))
+        self.assertEqual(difference.source_package_name.name, links[0].string)
+
 
 class TestMilestoneBatchNavigatorAttribute(TestCaseWithFactory):
     """Test the series.milestone_batch_navigator attribute."""

=== modified file 'lib/lp/registry/browser/tests/test_sourcepackage_views.py'
--- lib/lp/registry/browser/tests/test_sourcepackage_views.py	2010-09-02 11:34:34 +0000
+++ lib/lp/registry/browser/tests/test_sourcepackage_views.py	2010-09-29 09:44:52 +0000
@@ -61,7 +61,7 @@
 
     def test_get_register_upstream_url_summary(self):
         test_publisher = SoyuzTestPublisher()
-        test_data = test_publisher.makeSourcePackageWithBinaryPackageRelease()
+        test_data = test_publisher.makeSourcePackageSummaryData()
         source_package_name = (
             test_data['source_package'].sourcepackagename.name)
         distroseries_id = test_data['distroseries'].id

=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2010-09-23 01:46:29 +0000
+++ lib/lp/registry/configure.zcml	2010-09-29 09:44:52 +0000
@@ -114,7 +114,8 @@
         <allow interface="lp.registry.interfaces.distroseriesdifference.IDistroSeriesDifferencePublic"/>
         <require
             permission="launchpad.Edit"
-            interface="lp.registry.interfaces.distroseriesdifference.IDistroSeriesDifferenceEdit"/>
+            interface="lp.registry.interfaces.distroseriesdifference.IDistroSeriesDifferenceEdit"
+            set_attributes="package_diff parent_package_diff"/>
     </class>
 
     <!-- DistroSeriesDifferenceComment -->
@@ -172,21 +173,11 @@
         <!-- IStructuralSubscriptionTarget -->
 
         <allow
-            attributes="
-                bug_subscriptions
-                getBugNotificationsRecipients
-                getSubscription
-                getSubscriptions
-                parent_subscription_target
-                target_type_display
-                userHasBugSubscriptions
-                userCanAlterBugSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" />
         <require
             permission="launchpad.AnyPerson"
-            attributes="
-                addBugSubscription
-                addSubscription
-                removeBugSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" />
+
     </class>
     <securedutility
         class="lp.registry.model.distroseries.DistroSeriesSet"
@@ -317,21 +308,11 @@
         <!-- IStructuralSubscriptionTarget -->
 
         <allow
-            attributes="
-                bug_subscriptions
-                getSubscriptions
-                getSubscription
-                getBugNotificationsRecipients
-                parent_subscription_target
-                target_type_display
-                userHasBugSubscriptions
-                userCanAlterBugSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" />
         <require
             permission="launchpad.AnyPerson"
-            attributes="
-                addSubscription
-                addBugSubscription
-                removeBugSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" />
+
         <require
             permission="launchpad.Admin"
             set_attributes="registrant"/>
@@ -443,28 +424,24 @@
                 getBugCounts
                 bug_count
                 total_bug_heat
-                bug_subscriptions
-                getSubscriptions
-                getSubscription
-                parent_subscription_target
-                getBugNotificationsRecipients
                 getPersonsByEmail
                 getReleasesAndPublishingHistory
                 upstream_product
-                target_type_display
                 _getOfficialTagClause
                 official_bug_tags
                 findRelatedArchives
-                findRelatedArchivePublications
-                userHasBugSubscriptions
-                userCanAlterBugSubscription"/>
-        <require
-            permission="launchpad.AnyPerson"
-            attributes="
-                addSubscription
-                addBugSubscription
-                removeBugSubscription
-                createBug"/>
+                findRelatedArchivePublications"/>
+        <require
+            permission="launchpad.AnyPerson"
+            attributes="createBug"/>
+
+        <!-- IStructuralSubscriptionTarget -->
+
+        <allow
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" />
+        <require
+            permission="launchpad.AnyPerson"
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" />
 
         <!-- IQuestionTarget -->
 
@@ -961,21 +938,11 @@
         <!-- IStructuralSubscriptionTarget -->
 
         <allow
-            attributes="
-                bug_subscriptions
-                getSubscriptions
-                getSubscription
-                getBugNotificationsRecipients
-                parent_subscription_target
-                target_type_display
-                userHasBugSubscriptions
-                userCanAlterBugSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" />
         <require
             permission="launchpad.AnyPerson"
-            attributes="
-                addSubscription
-                addBugSubscription
-                removeBugSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" />
+
     </class>
 
     <!-- IMilestoneSet -->
@@ -1237,22 +1204,10 @@
         <!-- IStructuralSubscriptionTarget -->
 
         <allow
-            attributes="
-                bug_subscriptions
-                getSubscriptions
-                getSubscription
-                getBugNotificationsRecipients
-                parent_subscription_target
-                target_type_display
-                userHasBugSubscriptions
-                userCanAlterBugSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" />
         <require
             permission="launchpad.AnyPerson"
-            attributes="
-                addSubscription
-                addBugSubscription
-                removeBugSubscription
-                userCanAlterSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" />
 
         <!-- IHasBugSupervisor -->
 
@@ -1390,21 +1345,11 @@
         <!-- IStructuralSubscriptionTarget -->
 
         <allow
-            attributes="
-                bug_subscriptions
-                getBugNotificationsRecipients
-                getSubscription
-                getSubscriptions
-                parent_subscription_target
-                target_type_display
-                userHasBugSubscriptions
-                userCanAlterBugSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" />
         <require
             permission="launchpad.AnyPerson"
-            attributes="
-                addBugSubscription
-                addSubscription
-                removeBugSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" />
+
     </class>
     <adapter
         provides="canonical.launchpad.interfaces.IHasExternalBugTracker"
@@ -1517,22 +1462,10 @@
         <!-- IStructuralSubscriptionTarget -->
 
         <allow
-            attributes="
-                bug_subscriptions
-                getSubscriptions
-                getSubscription
-                getBugNotificationsRecipients
-                parent_subscription_target
-                target_type_display
-                userHasBugSubscriptions
-                userCanAlterBugSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" />
         <require
             permission="launchpad.AnyPerson"
-            attributes="
-                addSubscription
-                addBugSubscription
-                removeBugSubscription
-                userCanAlterSubscription"/>
+            interface="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" />
 
         <!-- IHasBugSupervisor -->
 

=== modified file 'lib/lp/registry/enum.py'
--- lib/lp/registry/enum.py	2010-08-27 08:17:00 +0000
+++ lib/lp/registry/enum.py	2010-09-29 09:44:52 +0000
@@ -63,15 +63,15 @@
         This difference is current and needs attention.
         """)
 
-    IGNORED = DBItem(2, """
-        Ignored
+    BLACKLISTED_CURRENT = DBItem(2, """
+        Blacklisted current version
 
         This difference is being ignored until a new package is uploaded
         or the status is manually updated.
         """)
 
-    IGNORED_ALWAYS = DBItem(3, """
-        Ignored always
+    BLACKLISTED_ALWAYS = DBItem(3, """
+        Blacklisted always
 
         This difference should always be ignored.
         """)

=== modified file 'lib/lp/registry/interfaces/distroseriesdifference.py'
--- lib/lp/registry/interfaces/distroseriesdifference.py	2010-09-01 12:12:13 +0000
+++ lib/lp/registry/interfaces/distroseriesdifference.py	2010-09-29 09:44:52 +0000
@@ -13,11 +13,21 @@
     'IDistroSeriesDifferenceSource',
     ]
 
+from lazr.restful.declarations import (
+    call_with,
+    export_as_webservice_entry,
+    export_write_operation,
+    exported,
+    operation_parameters,
+    REQUEST_USER,
+    )
 from lazr.restful.fields import Reference
 from zope.interface import Interface
 from zope.schema import (
+    Bool,
     Choice,
     Int,
+    Text,
     TextLine,
     )
 
@@ -39,11 +49,11 @@
 
     id = Int(title=_('ID'), required=True, readonly=True)
 
-    derived_series = Reference(
+    derived_series = exported(Reference(
         IDistroSeries, title=_("Derived series"), required=True,
         readonly=True, description=_(
             "The distribution series which, together with its parent, "
-            "identifies the two series with the difference."))
+            "identifies the two series with the difference.")))
 
     source_package_name = Reference(
         ISourcePackageName,
@@ -55,7 +65,14 @@
     package_diff = Reference(
         IPackageDiff, title=_("Package diff"), required=False,
         readonly=True, description=_(
-            "The most recently generated package diff for this difference."))
+            "The most recently generated package diff from the base to the "
+            "derived version."))
+
+    parent_package_diff = Reference(
+        IPackageDiff, title=_("Parent package diff"), required=False,
+        readonly=True, description=_(
+            "The most recently generated package diff from the base to the "
+            "parent version."))
 
     status = Choice(
         title=_('Distro series difference status.'),
@@ -102,7 +119,7 @@
         title=_("Title"), readonly=True, required=False, description=_(
             "A human-readable name describing this difference."))
 
-    def updateStatusAndType():
+    def update():
         """Checks that difference type and status matches current publishings.
 
         If the record is updated, a relevant comment is added.
@@ -120,13 +137,35 @@
 class IDistroSeriesDifferenceEdit(Interface):
     """Difference attributes requiring launchpad.Edit."""
 
-    def addComment(owner, comment):
+    @call_with(commenter=REQUEST_USER)
+    @operation_parameters(
+        comment=Text(title=_("Comment text"), required=True))
+    @export_write_operation()
+    def addComment(commenter, comment):
         """Add a comment on this difference."""
 
+    @operation_parameters(
+        all=Bool(title=_("All"), required=False))
+    @export_write_operation()
+    def blacklist(all=False):
+        """Blacklist this version or all versions of this source package.
+
+        :param all: indicates whether all versions of this package should
+            be blacklisted or just the current (default).
+        """
+
+    @export_write_operation()
+    def unblacklist():
+        """Removes this difference from the blacklist.
+
+        The status will be updated based on the versions.
+        """
+
 
 class IDistroSeriesDifference(IDistroSeriesDifferencePublic,
                               IDistroSeriesDifferenceEdit):
     """An interface for a package difference between two distroseries."""
+    export_as_webservice_entry()
 
 
 class IDistroSeriesDifferenceSource(Interface):
@@ -170,3 +209,13 @@
         :type status: `DistroSeriesDifferenceStatus`.
         :return: A result set of differences.
         """
+
+    def getByDistroSeriesAndName(distro_series, source_package_name):
+        """Returns a single difference matching the series and name.
+
+        :param distro_series: The derived distribution series which is to be
+            searched for differences.
+        :type distro_series: `IDistroSeries`.
+        :param source_package_name: The name of the package difference.
+        :type source_package_name: unicode.
+        """

=== modified file 'lib/lp/registry/interfaces/distroseriesdifferencecomment.py'
--- lib/lp/registry/interfaces/distroseriesdifferencecomment.py	2010-08-31 15:56:29 +0000
+++ lib/lp/registry/interfaces/distroseriesdifferencecomment.py	2010-09-29 09:44:52 +0000
@@ -10,9 +10,14 @@
     ]
 
 
+from lazr.restful.declarations import (
+    export_as_webservice_entry,
+    exported,
+    )
 from lazr.restful.fields import Reference
 from zope.interface import Interface
 from zope.schema import (
+    Datetime,
     Int,
     Text,
     )
@@ -26,6 +31,7 @@
 
 class IDistroSeriesDifferenceComment(Interface):
     """A comment for a distroseries difference record."""
+    export_as_webservice_entry()
 
     id = Int(title=_('ID'), required=True, readonly=True)
 
@@ -38,9 +44,17 @@
         IMessage, title=_("Message"), required=True, readonly=True,
         description=_("A comment about this difference."))
 
-    comment = Text(
+    body_text = exported(Text(
         title=_("Comment text"), readonly=True, description=_(
-            "The comment text for the related distro series difference."))
+            "The comment text for the related distro series difference.")))
+
+    comment_author = exported(Reference(
+        # Really IPerson.
+        Interface, title=_("The author of the comment."),
+        readonly=True))
+
+    comment_date = exported(Datetime(
+        title=_('Comment date.'), readonly=True))
 
 
 class IDistroSeriesDifferenceCommentSource(Interface):
@@ -55,3 +69,6 @@
         :param comment: The comment.
         :return: A new `DistroSeriesDifferenceComment` object.
         """
+
+    def getForDifference(distro_series_difference, id):
+        """Return the `IDistroSeriesDifferenceComment` with the given id."""

=== modified file 'lib/lp/registry/interfaces/productseries.py'
--- lib/lp/registry/interfaces/productseries.py	2010-09-21 13:05:42 +0000
+++ lib/lp/registry/interfaces/productseries.py	2010-09-29 09:44:52 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 # pylint: disable-msg=E0211,E0213
@@ -249,15 +249,6 @@
         schema=IBranch,
         required=False,
         description=_(
-            "A Bazaar branch to commit translation snapshots to. "
-            "Leave blank to disable."))
-
-    translations_branch = ReferenceChoice(
-        title=_("Translations export branch"),
-        vocabulary='HostedBranchRestrictedOnOwner',
-        schema=IBranch,
-        required=False,
-        description=_(
             "A Bazaar branch to commit translation snapshots to.  "
             "Leave blank to disable."))
 

=== modified file 'lib/lp/registry/interfaces/structuralsubscription.py'
--- lib/lp/registry/interfaces/structuralsubscription.py	2010-08-20 20:31:18 +0000
+++ lib/lp/registry/interfaces/structuralsubscription.py	2010-09-29 09:44:52 +0000
@@ -129,9 +129,11 @@
         title=_("The structure to which this subscription belongs.")))
 
 
-class IStructuralSubscriptionTarget(Interface):
-    """A Launchpad Structure allowing users to subscribe to it."""
-    export_as_webservice_entry()
+class IStructuralSubscriptionTargetRead(Interface):
+    """A Launchpad Structure allowing users to subscribe to it.
+
+    Read-only parts.
+    """
 
     # We don't really want to expose the level details yet. Only
     # BugNotificationLevel.COMMENTS is used at this time.
@@ -155,9 +157,47 @@
     parent_subscription_target = Attribute(
         "The target's parent, or None if one doesn't exist.")
 
+    bug_subscriptions = Attribute(
+        "All subscriptions to bugs at the METADATA level or higher.")
+
     def userCanAlterSubscription(subscriber, subscribed_by):
         """Check if a user can change a subscription for a person."""
 
+    def userCanAlterBugSubscription(subscriber, subscribed_by):
+        """Check if a user can change a bug subscription for a person."""
+
+    @operation_parameters(person=Reference(schema=IPerson))
+    @operation_returns_entry(IStructuralSubscription)
+    @export_read_operation()
+    def getSubscription(person):
+        """Return the subscription for `person`, if it exists."""
+
+    def getBugNotificationsRecipients(recipients=None, level=None):
+        """Return the set of bug subscribers to this target.
+
+        :param recipients: If recipients is not None, a rationale
+            is added for each subscriber.
+        :type recipients: `INotificationRecipientSet`
+        'param level: If level is not None, only strucutral
+            subscribers with a subscrition level greater or equal
+            to the given value are returned.
+        :type level: `BugNotificationLevel`
+        :return: An `INotificationRecipientSet` instance containing
+            the bug subscribers.
+        """
+
+    target_type_display = Attribute("The type of the target, for display.")
+
+    def userHasBugSubscriptions(user):
+        """Is `user` subscribed, directly or via a team, to bug mail?"""
+
+
+class IStructuralSubscriptionTargetWrite(Interface):
+    """A Launchpad Structure allowing users to subscribe to it.
+
+    Modify-only parts.
+    """
+
     def addSubscription(subscriber, subscribed_by):
         """Add a subscription for this structure.
 
@@ -170,9 +210,6 @@
         :return: The new subscription.
         """
 
-    def userCanAlterBugSubscription(subscriber, subscribed_by):
-        """Check if a user can change a bug subscription for a person."""
-
     @operation_parameters(
         subscriber=Reference(
             schema=IPerson,
@@ -216,30 +253,11 @@
         :unsubscribed_by: The IPerson removing the subscription.
         """
 
-    @operation_parameters(person=Reference(schema=IPerson))
-    @operation_returns_entry(IStructuralSubscription)
-    @export_read_operation()
-    def getSubscription(person):
-        """Return the subscription for `person`, if it exists."""
-
-    def getBugNotificationsRecipients(recipients=None, level=None):
-        """Return the set of bug subscribers to this target.
-
-        :param recipients: If recipients is not None, a rationale
-            is added for each subscriber.
-        :type recipients: `INotificationRecipientSet`
-        'param level: If level is not None, only strucutral
-            subscribers with a subscrition level greater or equal
-            to the given value are returned.
-        :type level: `BugNotificationLevel`
-        :return: An `INotificationRecipientSet` instance containing
-            the bug subscribers.
-        """
-
-    target_type_display = Attribute("The type of the target, for display.")
-
-    def userHasBugSubscriptions(user):
-        """Is `user` subscribed, directly or via a team, to bug mail?"""
+
+class IStructuralSubscriptionTarget(IStructuralSubscriptionTargetRead,
+                                    IStructuralSubscriptionTargetWrite):
+    """A Launchpad Structure allowing users to subscribe to it."""
+    export_as_webservice_entry()
 
 
 class IStructuralSubscriptionForm(Interface):

=== added file 'lib/lp/registry/javascript/distroseriesdifferences_details.js'
--- lib/lp/registry/javascript/distroseriesdifferences_details.js	1970-01-01 00:00:00 +0000
+++ lib/lp/registry/javascript/distroseriesdifferences_details.js	2010-09-29 09:44:52 +0000
@@ -0,0 +1,180 @@
+/* Copyright 2010 Canonical Ltd.  This software is licensed under the
+ * GNU Affero General Public License version 3 (see the file LICENSE).
+ *
+ * Enhancements for the distroseries differences page.
+ *
+ * @module registry
+ * @submodule distroseriesdifferences_details
+ * @requires  io-base, lp.soyuz.base
+ */
+YUI.add('lp.registry.distroseriesdifferences_details', function(Y) {
+
+var namespace = Y.namespace('lp.registry.distroseriesdifferences_details');
+
+/**
+ * Create one Launchpad client that will be used with multiple requests.
+ */
+var lp_client = new LP.client.Launchpad();
+
+/*
+ * Setup the expandable rows for each difference.
+ *
+ * @method setup_expandable_rows
+ */
+namespace.setup_expandable_rows = function() {
+
+    var blacklist_handler = function(e, api_uri, source_name) {
+        // We only want to select the new radio if the update is
+        // successful.
+        e.preventDefault();
+        var blacklist_options_container = this.ancestor('div');
+
+        // Disable all the inputs
+        blacklist_options_container.all('input').set('disabled', 'disabled');
+        e.target.prepend('<img src="/@@/spinner" />');
+
+        var method_name = (e.target.get('value') == 'NONE') ?
+            'unblacklist' : 'blacklist';
+        var blacklist_all = (e.target.get('value') == 'BLACKLISTED_ALWAYS');
+
+        var diff_rows = Y.all('tr.' + source_name);
+
+        var config = {
+            on: {
+                success: function(updated_entry, args) {
+                    // Let the user know this item is now blacklisted.
+                    blacklist_options_container.one('img').remove();
+                    blacklist_options_container.all(
+                        'input').set('disabled', false);
+                    e.target.set('checked', true);
+                    Y.each(diff_rows, function(diff_row) {
+                        var fade_to_gray = new Y.Anim({
+                            node: diff_row,
+                            from: { backgroundColor: '#FFFFFF'},
+                            to: { backgroundColor: '#EEEEEE'}
+                            });
+                        if (method_name == 'unblacklist') {
+                            fade_to_gray.set('reverse', true);
+                            }
+                        fade_to_gray.run();
+                        });
+                },
+                failure: function(id, response) {
+                    blacklist_options_container.one('img').remove();
+                    blacklist_options_container.all(
+                        'input').set('disabled', false);
+                }
+            },
+            parameters: {
+                all: blacklist_all
+            }
+        };
+
+        lp_client.named_post(api_uri, method_name, config);
+
+    };
+
+    /**
+     * Link the click event for these blacklist options to the correct
+     * api uri.
+     *
+     * @param blacklist_options {Node} The node containing the blacklist
+     *                          options.
+     * @param source_name {string} The name of the source to update.
+     */
+    var setup_blacklist_options = function(blacklist_options,
+                                                 source_name) {
+        var api_uri = [
+            LP.client.cache.context.self_link,
+            '+difference',
+            source_name
+            ].join('/')
+        Y.on('click', blacklist_handler, blacklist_options.all('input'),
+             blacklist_options, api_uri, source_name);
+    };
+
+    /**
+     * Get the extra information for this diff to display.
+     *
+     * @param uri {string} The uri for the extra diff info.
+     * @param container {Node} A node which must contain a div with the
+     *                  class 'diff-extra-container' into which the results
+     *                  are inserted.
+     */
+    var get_extra_diff_info = function(uri, container, source_name) {
+
+        var in_progress_message = Y.lp.soyuz.base.makeInProgressNode(
+            'Fetching difference details ...')
+        container.one('div.diff-extra-container').insert(
+            in_progress_message, 'replace');
+
+        var success_cb = function(transaction_id, response, args) {
+            args.container.one('div.diff-extra-container').insert(
+                response.responseText, 'replace');
+            setup_blacklist_options(args.container.one(
+                'div.blacklist-options'), source_name);
+            };
+
+        var failure_cb = function(transaction_id, response, args){
+           var retry_handler = function(e) {
+               e.preventDefault();
+               get_extra_diff_info(args.uri, args.container);
+               };
+           var failure_message = Y.lp.soyuz.base.makeFailureNode(
+               'Failed to fetch difference details.',
+               retry_handler);
+           args.container.insert(failure_message, 'replace');
+
+           var anim = Y.lazr.anim.red_flash({
+                node: args.container
+                });
+           anim.run();
+           };
+
+        var config = {
+            on: {
+                'success': success_cb,
+                'failure': failure_cb,
+            },
+            arguments: {
+                'container': container,
+                'uri': uri
+            }
+        };
+        Y.io(uri, config);
+
+    };
+
+    var expander_handler = function(e) {
+        e.preventDefault();
+        var toggle = e.currentTarget;
+        var row = toggle.ancestor('tr');
+        toggle.toggleClass('treeCollapsed').toggleClass('treeExpanded');
+
+        // Only insert if there isn't already a container row there.
+        next_row = row.next();
+        if (next_row == null || !next_row.hasClass('diff-extra')) {
+            var source_name = row.one('a.toggle-extra').get('text');
+            var details_row = Y.Node.create([
+                '<table><tr class="diff-extra unseen ' + source_name + '">',
+                '  <td colspan="5">',
+                '    <div class="diff-extra-container"></div>',
+                '  </td></tr></table>'
+                ].join('')).one('tr');
+            row.insert(details_row, 'after');
+            var uri = toggle.get('href');
+            get_extra_diff_info(uri, details_row.one('td'), source_name);
+        } else {
+            details_row = next_row
+        }
+        details_row.toggleClass('unseen');
+    };
+
+    Y.all('table.listing a.toggle-extra').each(function(toggle){
+        toggle.addClass('treeCollapsed').addClass('sprite');
+        toggle.on("click", expander_handler);
+    })
+
+};
+
+}, "0.1", {"requires": ["io-base", "lp.soyuz.base", "lazr.anim"]});

=== modified file 'lib/lp/registry/model/distroseriesdifference.py'
--- lib/lp/registry/model/distroseriesdifference.py	2010-09-06 16:04:55 +0000
+++ lib/lp/registry/model/distroseriesdifference.py	2010-09-29 09:44:52 +0000
@@ -15,6 +15,7 @@
     Int,
     Reference,
     Storm,
+    Unicode,
     )
 from zope.component import getUtility
 from zope.interface import (
@@ -41,6 +42,11 @@
     )
 from lp.registry.model.distroseriesdifferencecomment import (
     DistroSeriesDifferenceComment)
+from lp.registry.model.sourcepackagename import SourcePackageName
+from lp.services.propertycache import (
+    cachedproperty,
+    IPropertyCacheManager,
+    )
 
 
 class DistroSeriesDifference(Storm):
@@ -65,10 +71,18 @@
     package_diff = Reference(
         package_diff_id, 'PackageDiff.id')
 
+    parent_package_diff_id = Int(
+        name='parent_package_diff', allow_none=True)
+    parent_package_diff = Reference(
+        parent_package_diff_id, 'PackageDiff.id')
+
     status = DBEnum(name='status', allow_none=False,
                     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)
 
     @staticmethod
     def new(derived_series, source_package_name, difference_type,
@@ -84,6 +98,14 @@
         diff.status = status
         diff.difference_type = difference_type
 
+        source_pub = diff.source_pub
+        if source_pub is not None:
+            diff.source_version = source_pub.source_package_version
+        parent_source_pub = diff.parent_source_pub
+        if parent_source_pub is not None:
+            diff.parent_source_version = (
+                parent_source_pub.source_package_version)
+
         return store.add(diff)
 
     @staticmethod
@@ -105,12 +127,22 @@
             DistroSeriesDifference.difference_type == difference_type,
             DistroSeriesDifference.status.is_in(status))
 
-    @property
+    @staticmethod
+    def getByDistroSeriesAndName(distro_series, source_package_name):
+        """See `IDistroSeriesDifferenceSource`."""
+        return IStore(DistroSeriesDifference).find(
+            DistroSeriesDifference,
+            DistroSeriesDifference.derived_series == distro_series,
+            DistroSeriesDifference.source_package_name == (
+                SourcePackageName.id),
+            SourcePackageName.name == source_package_name).one()
+
+    @cachedproperty
     def source_pub(self):
         """See `IDistroSeriesDifference`."""
         return self._getLatestSourcePub()
 
-    @property
+    @cachedproperty
     def parent_source_pub(self):
         """See `IDistroSeriesDifference`."""
         return self._getLatestSourcePub(for_parent=True)
@@ -149,22 +181,25 @@
         else:
             return None
 
-    @property
-    def source_version(self):
-        """See `IDistroSeriesDifference`."""
-        if self.source_pub:
-            return self.source_pub.source_package_version
-        return None
-
-    @property
-    def parent_source_version(self):
-        """See `IDistroSeriesDifference`."""
-        if self.parent_source_pub:
-            return self.parent_source_pub.source_package_version
-        return None
-
-    def updateStatusAndType(self):
-        """See `IDistroSeriesDifference`."""
+    def update(self):
+        """See `IDistroSeriesDifference`."""
+        # Updating is expected to be a heavy operation (not called
+        # during requests). We clear the cache beforehand - even though
+        # it is not currently necessary - so that in the future it
+        # won't cause a hard-to find bug if a script ever creates a
+        # difference, copies/publishes a new version and then calls
+        # update() (like the tests for this method do).
+        IPropertyCacheManager(self).clear()
+        self._updateType()
+        updated = self._updateVersionsAndStatus()
+        return updated
+
+    def _updateType(self):
+        """Helper for update() interface method.
+
+        Check whether the presence of a source in the derived or parent
+        series has changed (which changes the type of difference).
+        """
         if self.source_pub is None:
             new_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
         elif self.parent_source_pub is None:
@@ -172,28 +207,56 @@
         else:
             new_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
 
-        updated = False
         if new_type != self.difference_type:
-            updated = True
             self.difference_type = new_type
 
-        version = self.source_version
-        parent_version = self.parent_source_version
+    def _updateVersionsAndStatus(self):
+        """Helper for the update() interface method.
+
+        Check whether the status of this difference should be updated.
+        """
+        updated = False
+        new_source_version = new_parent_source_version = None
+        if self.source_pub:
+            new_source_version = self.source_pub.source_package_version
+            if self.source_version != new_source_version:
+                self.source_version = new_source_version
+                updated = True
+                # If the derived version has change and the previous version
+                # was blacklisted, then we remove the blacklist now.
+                if self.status == (
+                    DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT):
+                    self.status = DistroSeriesDifferenceStatus.NEEDS_ATTENTION
+        if self.parent_source_pub:
+            new_parent_source_version = (
+                self.parent_source_pub.source_package_version)
+            if self.parent_source_version != new_parent_source_version:
+                self.parent_source_version = new_parent_source_version
+                updated = True
+
+        # If this difference was resolved but now the versions don't match
+        # then we re-open the difference.
         if self.status == DistroSeriesDifferenceStatus.RESOLVED:
-            if version != parent_version:
+            if self.source_version != self.parent_source_version:
                 updated = True
                 self.status = DistroSeriesDifferenceStatus.NEEDS_ATTENTION
-        else:
-            if version == parent_version:
+        # If this difference was needing attention, or the current version
+        # was blacklisted and the versions now match we resolve it. Note:
+        # we don't resolve it if this difference was blacklisted for all
+        # versions.
+        elif self.status in (
+            DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
+            DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT):
+            if self.source_version == self.parent_source_version:
                 updated = True
                 self.status = DistroSeriesDifferenceStatus.RESOLVED
 
         return updated
 
-    def addComment(self, owner, comment):
+    def addComment(self, commenter, comment):
         """See `IDistroSeriesDifference`."""
         return getUtility(IDistroSeriesDifferenceCommentSource).new(
-            self, owner, comment)
+            self, commenter, comment)
 
     def getComments(self):
         """See `IDistroSeriesDifference`."""
@@ -202,3 +265,15 @@
             DistroSeriesDifferenceComment,
             DSDComment.distro_series_difference == self)
         return comments.order_by(Desc(DSDComment.id))
+
+    def blacklist(self, all=False):
+        """See `IDistroSeriesDifference`."""
+        if all:
+            self.status = DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS
+        else:
+            self.status = DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT
+
+    def unblacklist(self):
+        """See `IDistroSeriesDifference`."""
+        self.status = DistroSeriesDifferenceStatus.NEEDS_ATTENTION
+        self.update()

=== modified file 'lib/lp/registry/model/distroseriesdifferencecomment.py'
--- lib/lp/registry/model/distroseriesdifferencecomment.py	2010-08-31 15:56:29 +0000
+++ lib/lp/registry/model/distroseriesdifferencecomment.py	2010-09-29 09:44:52 +0000
@@ -22,7 +22,10 @@
     )
 
 from canonical.launchpad.database.message import Message, MessageChunk
-from canonical.launchpad.interfaces.lpstorm import IMasterStore
+from canonical.launchpad.interfaces.lpstorm import (
+    IMasterStore,
+    IStore,
+    )
 from lp.registry.interfaces.distroseriesdifferencecomment import (
     IDistroSeriesDifferenceComment,
     IDistroSeriesDifferenceCommentSource,
@@ -46,10 +49,20 @@
     message = Reference(message_id, 'Message.id')
 
     @property
-    def comment(self):
+    def comment_author(self):
+        """See `IDistroSeriesDifferenceComment`."""
+        return self.message.owner
+
+    @property
+    def body_text(self):
         """See `IDistroSeriesDifferenceComment`."""
         return self.message.text_contents
 
+    @property
+    def comment_date(self):
+        """See `IDistroSeriesDifferenceComment`."""
+        return self.message.datecreated
+
     @staticmethod
     def new(distro_series_difference, owner, comment):
         """See `IDistroSeriesDifferenceCommentSource`."""
@@ -65,3 +78,13 @@
         dsd_comment.message = message
 
         return store.add(dsd_comment)
+
+    @staticmethod
+    def getForDifference(distro_series_difference, id):
+        """See `IDistroSeriesDifferenceCommentSource`."""
+        store = IStore(DistroSeriesDifferenceComment)
+        DSDComment = DistroSeriesDifferenceComment
+        return store.find(
+            DSDComment,
+            DSDComment.distro_series_difference == distro_series_difference,
+            DSDComment.id == id).one()

=== modified file 'lib/lp/registry/model/milestone.py'
--- lib/lp/registry/model/milestone.py	2010-09-22 08:48:24 +0000
+++ lib/lp/registry/model/milestone.py	2010-09-29 09:44:52 +0000
@@ -15,6 +15,7 @@
 
 import datetime
 
+from lazr.restful.error import expose
 from sqlobject import (
     AND,
     BoolCol,
@@ -28,6 +29,7 @@
     And,
     Store,
     )
+from storm.zope import IResultSet
 from zope.component import getUtility
 from zope.interface import implements
 
@@ -36,7 +38,6 @@
     sqlvalues,
     )
 from canonical.launchpad.webapp.sorting import expand_numbers
-from lazr.restful.error import expose
 from lp.app.errors import NotFoundError
 from lp.blueprints.model.specification import Specification
 from lp.bugs.interfaces.bugtarget import IHasBugs
@@ -230,7 +231,8 @@
         """See `IMilestone`."""
         params = BugTaskSearchParams(milestone=self, user=None)
         bugtasks = getUtility(IBugTaskSet).search(params)
-        assert len(self.getSubscriptions()) == 0, (
+        subscriptions = IResultSet(self.getSubscriptions())
+        assert subscriptions.is_empty(), (
             "You cannot delete a milestone which has structural "
             "subscriptions.")
         assert bugtasks.count() == 0, (
@@ -257,7 +259,7 @@
         """See lp.registry.interfaces.milestone.IMilestoneSet."""
         try:
             return Milestone.get(milestoneid)
-        except SQLObjectNotFound, err:
+        except SQLObjectNotFound:
             raise NotFoundError(
                 "Milestone with ID %d does not exist" % milestoneid)
 

=== modified file 'lib/lp/registry/model/structuralsubscription.py'
--- lib/lp/registry/model/structuralsubscription.py	2010-08-20 20:31:18 +0000
+++ lib/lp/registry/model/structuralsubscription.py	2010-09-29 09:44:52 +0000
@@ -266,47 +266,36 @@
                          min_blueprint_notification_level=
                          BlueprintNotificationLevel.NOTHING):
         """See `IStructuralSubscriptionTarget`."""
-        target_clause_parts = []
-        for key, value in self._target_args.items():
+        clauses = [
+            "StructuralSubscription.subscriber = Person.id",
+            "StructuralSubscription.bug_notification_level "
+            ">= %s" % quote(min_bug_notification_level),
+            "StructuralSubscription.blueprint_notification_level "
+            ">= %s" % quote(min_blueprint_notification_level),
+            ]
+        for key, value in self._target_args.iteritems():
             if value is None:
-                target_clause_parts.append(
-                    "StructuralSubscription.%s IS NULL " % (key, ))
+                clauses.append(
+                    "StructuralSubscription.%s IS NULL" % (key,))
             else:
-                target_clause_parts.append(
-                    "StructuralSubscription.%s = %s " % (key, quote(value)))
-        target_clause = " AND ".join(target_clause_parts)
-        query = target_clause + """
-            AND StructuralSubscription.subscriber = Person.id
-            """
-        all_subscriptions = StructuralSubscription.select(
-            query,
-            orderBy='Person.displayname',
-            clauseTables=['Person'])
-        subscriptions = [sub for sub
-                         in all_subscriptions
-                         if ((sub.bug_notification_level >=
-                             min_bug_notification_level) and
-                             (sub.blueprint_notification_level >=
-                              min_blueprint_notification_level))]
-        return subscriptions
+                clauses.append(
+                    "StructuralSubscription.%s = %s" % (key, quote(value)))
+        query = " AND ".join(clauses)
+        return StructuralSubscription.select(
+            query, orderBy='Person.displayname', clauseTables=['Person'])
 
     def getBugNotificationsRecipients(self, recipients=None, level=None):
         """See `IStructuralSubscriptionTarget`."""
-        subscribers = set()
         if level is None:
             subscriptions = self.bug_subscriptions
         else:
             subscriptions = self.getSubscriptions(
                 min_bug_notification_level=level)
-        for subscription in subscriptions:
-            if (level is not None and
-                subscription.bug_notification_level < level):
-                continue
-            subscriber = subscription.subscriber
-            subscribers.add(subscriber)
-            if recipients is not None:
-                recipients.addStructuralSubscriber(
-                    subscriber, self)
+        subscribers = set(
+            subscription.subscriber for subscription in subscriptions)
+        if recipients is not None:
+            for subscriber in subscribers:
+                recipients.addStructuralSubscriber(subscriber, self)
         parent = self.parent_subscription_target
         if parent is not None:
             subscribers.update(

=== modified file 'lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt'
--- lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt	2010-08-09 23:05:53 +0000
+++ lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt	2010-09-29 09:44:52 +0000
@@ -6,7 +6,7 @@
     >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
     >>> test_publisher = SoyuzTestPublisher()
     >>> login('admin@xxxxxxxxxxxxx')
-    >>> test_data = test_publisher.makeSourcePackageWithBinaryPackageRelease()
+    >>> test_data = test_publisher.makeSourcePackageSummaryData()
     >>> test_publisher.updateDistroSeriesPackageCache(
     ...     test_data['distroseries'])
     >>> logout()

=== modified file 'lib/lp/registry/templates/distroseries-localdifferences.pt'
--- lib/lp/registry/templates/distroseries-localdifferences.pt	2010-09-08 09:18:11 +0000
+++ lib/lp/registry/templates/distroseries-localdifferences.pt	2010-09-29 09:44:52 +0000
@@ -44,15 +44,17 @@
             <tal:difference repeat="difference differences/batch">
             <tr tal:define="parent_source_pub difference/parent_source_pub;
                             source_pub difference/source_pub;
-                            signer source_pub/sourcepackagerelease/uploader/fmt:link|nothing;">
-              <td><span
-                      tal:replace="parent_source_pub/source_package_name">Foo</span>
+                            signer source_pub/sourcepackagerelease/uploader/fmt:link|nothing;"
+                tal:attributes="class parent_source_pub/source_package_name">
+              <td><a tal:attributes="href difference/fmt:url" class="toggle-extra"
+                     tal:content="parent_source_pub/source_package_name">Foo</a>
               </td>
               <td><a tal:attributes="href parent_source_pub/sourcepackagerelease/fmt:url">
                     <tal:replace
                     replace="parent_source_pub/sourcepackagerelease/version"/></a>
               </td>
-              <td><a tal:attributes="href source_pub/sourcepackagerelease/fmt:url">
+              <td><a tal:attributes="href source_pub/sourcepackagerelease/fmt:url"
+                     class="derived-version">
                     <tal:replace
                     replace="source_pub/sourcepackagerelease/version"/></a>
               </td>
@@ -66,11 +68,11 @@
               <td>
                 <tal:comment tal:define="comment python:difference.getComments().first();"
                              tal:condition="comment">
-                  <span tal:replace="comment/comment/fmt:shorten/50">I'm on this.</span>
+                  <span tal:replace="comment/body_text/fmt:shorten/50">I'm on this.</span>
                   <br /><span class="greyed-out greylink"><span
-                      tal:replace="comment/message/datecreated/fmt:approximatedate">2005-09-16</span>
+                      tal:replace="comment/comment_date/fmt:approximatedate">2005-09-16</span>
                   by <a tal:replace="structure
-                      comment/message/owner/fmt:link">joesmith</a></span>
+                      comment/comment_author/fmt:link">joesmith</a></span>
                 </tal:comment>
               </td>
             </tr>
@@ -79,6 +81,13 @@
           </tbody>
         </table>
       </div>
+<script type="text/javascript">
+LPS.use('lp.registry.distroseriesdifferences_details', function(Y) {
+  diff_module = Y.lp.registry.distroseriesdifferences_details
+
+  Y.on('domready', diff_module.setup_expandable_rows);
+});
+</script>
 
     </div>
 

=== added file 'lib/lp/registry/templates/distroseriesdifference-listing-extra.pt'
--- lib/lp/registry/templates/distroseriesdifference-listing-extra.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/registry/templates/distroseriesdifference-listing-extra.pt	2010-09-29 09:44:52 +0000
@@ -0,0 +1,58 @@
+<tal:root
+  xmlns:tal="http://xml.zope.org/namespaces/tal";
+  xmlns:metal="http://xml.zope.org/namespaces/metal";
+  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+  i18n:domain="launchpad">
+
+  <tal:show_options condition="view/show_edit_options">
+  <div class="blacklist-options" style="float:right">
+      <dl>
+        <dt>Blacklisted:</dt>
+        <dd>
+          <form><tal:replace replace="structure view/widgets/blacklist_options" /></form>
+        </dd>
+      </dl>
+  </div>
+  </tal:show_options>
+
+  <dl>
+    <dt>Binary descriptions:</dt>
+      <dd><ul>
+        <li tal:repeat="summary view/binary_summaries">
+          <tal:description replace="summary" /></li>
+      </ul></dd>
+    <dt>Last common version:</dt>
+    <dd>Not implemented.</dd>
+    <dt>Differences from last common version:</dt>
+    <dd>
+      <ul>
+        <tal:source-diff-option condition="context/source_pub">
+        <li tal:condition="context/package_diff"
+            tal:content="structure context/package_diff/fmt:link">
+          <a>link to a diff</a></li>
+        <li tal:condition="not: context/package_diff" class="request-derived-diff">
+          <span tal:replace="context/derived_series/displayname">
+            Derilucid</span> version: <span
+          tal:replace="context/source_version">1.2.3</span>
+        </li>
+        </tal:source-diff-option>
+
+        <tal:parent-diff-option condition="context/parent_source_pub">
+        <li tal:condition="context/parent_package_diff"
+            tal:content="structure context/parent_package_diff/fmt:link">
+          <a>link to a diff</a></li>
+        <li tal:condition="not: context/parent_package_diff" class="request-derived-diff">
+          <span
+              tal:replace="context/derived_series/parent_series/displayname">
+              Lucid</span> version: <span
+              tal:replace="context/parent_source_version">1.2.3</span>
+        </li>
+        </tal:parent-diff-option>
+      </ul>
+    </dd>
+  </dl>
+
+  <h2>Comments:</h2>
+  <tal:conversation replace="structure view/@@+render"/>
+
+</tal:root>

=== modified file 'lib/lp/registry/tests/test_distroseriesdifference.py'
--- lib/lp/registry/tests/test_distroseriesdifference.py	2010-09-06 16:04:55 +0000
+++ lib/lp/registry/tests/test_distroseriesdifference.py	2010-09-29 09:44:52 +0000
@@ -9,6 +9,7 @@
 
 import unittest
 
+from storm.exceptions import IntegrityError
 from storm.store import Store
 from zope.component import getUtility
 from zope.security.interfaces import Unauthorized
@@ -16,10 +17,6 @@
 from canonical.launchpad.webapp.authorization import check_permission
 from canonical.launchpad.webapp.testing import verifyObject
 from canonical.testing import DatabaseFunctionalLayer
-from lp.testing import (
-    person_logged_in,
-    TestCaseWithFactory,
-    )
 from lp.registry.enum import (
     DistroSeriesDifferenceStatus,
     DistroSeriesDifferenceType,
@@ -29,7 +26,12 @@
     IDistroSeriesDifference,
     IDistroSeriesDifferenceSource,
     )
+from lp.services.propertycache import IPropertyCacheManager
 from lp.soyuz.interfaces.publishing import PackagePublishingStatus
+from lp.testing import (
+    person_logged_in,
+    TestCaseWithFactory,
+    )
 
 
 class DistroSeriesDifferenceTestCase(TestCaseWithFactory):
@@ -132,7 +134,7 @@
 
         self.assertEqual(None, ds_diff.source_version)
 
-    def test_updateStatusAndType_resolves_difference(self):
+    def test_update_resolves_difference(self):
         # Status is set to resolved when versions match.
         ds_diff = self.factory.makeDistroSeriesDifference(
             source_package_name_str="foonew",
@@ -146,14 +148,14 @@
             status=PackagePublishingStatus.PENDING,
             version='1.0')
 
-        was_updated = ds_diff.updateStatusAndType()
+        was_updated = ds_diff.update()
 
         self.assertTrue(was_updated)
         self.assertEqual(
             DistroSeriesDifferenceStatus.RESOLVED,
             ds_diff.status)
 
-    def test_updateStatusAndType_re_opens_difference(self):
+    def test_update_re_opens_difference(self):
         # The status of a resolved difference will updated with new
         # uploads.
         ds_diff = self.factory.makeDistroSeriesDifference(
@@ -169,18 +171,16 @@
             status=PackagePublishingStatus.PENDING,
             version='1.1')
 
-        was_updated = ds_diff.updateStatusAndType()
+        was_updated = ds_diff.update()
 
         self.assertTrue(was_updated)
         self.assertEqual(
             DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
             ds_diff.status)
 
-    def test_updateStatusAndType_new_version_no_change(self):
-        # Uploading a new (different) version does not necessarily
-        # update the record.
-        # In this case, a new version is uploaded, but there is still a
-        # difference needing attention.
+    def test_update_new_version_doesnt_change_status(self):
+        # Uploading a new (different) version does not update the
+        # status of the record, but the version is updated.
         ds_diff = self.factory.makeDistroSeriesDifference(
             source_package_name_str="foonew",
             versions={
@@ -193,14 +193,15 @@
             status=PackagePublishingStatus.PENDING,
             version='1.1')
 
-        was_updated = ds_diff.updateStatusAndType()
+        was_updated = ds_diff.update()
 
-        self.assertFalse(was_updated)
+        self.assertTrue(was_updated)
         self.assertEqual(
             DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
             ds_diff.status)
+        self.assertEqual('1.1', ds_diff.source_version)
 
-    def test_updateStatusAndType_changes_type(self):
+    def test_update_changes_type(self):
         # The type of difference is updated when appropriate.
         # In this case, a package that was previously only in the
         # derived series (UNIQUE_TO_DERIVED_SERIES), is uploaded
@@ -218,13 +219,62 @@
             status=PackagePublishingStatus.PENDING,
             version='1.1')
 
-        was_updated = ds_diff.updateStatusAndType()
+        was_updated = ds_diff.update()
 
         self.assertTrue(was_updated)
         self.assertEqual(
             DistroSeriesDifferenceType.DIFFERENT_VERSIONS,
             ds_diff.difference_type)
 
+    def test_update_removes_version_blacklist(self):
+        # A blacklist on a version of a package is removed when a new
+        # version is uploaded to the derived series.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            source_package_name_str="foonew",
+            versions={
+                'derived': '0.9',
+                },
+            difference_type=(
+                DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES),
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
+        new_derived_pub = self.factory.makeSourcePackagePublishingHistory(
+            sourcepackagename=ds_diff.source_package_name,
+            distroseries=ds_diff.derived_series,
+            status=PackagePublishingStatus.PENDING,
+            version='1.1')
+
+        was_updated = ds_diff.update()
+
+        self.assertTrue(was_updated)
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
+            ds_diff.status)
+
+    def test_update_does_not_remove_permanent_blacklist(self):
+        # A permanent blacklist is not removed when a new version
+        # is uploaded, even if it resolves the difference (as later
+        # uploads could re-create a difference, and we want to keep
+        # the blacklist).
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            source_package_name_str="foonew",
+            versions={
+                'derived': '0.9',
+                'parent': '1.0',
+                },
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS)
+        new_derived_pub = self.factory.makeSourcePackagePublishingHistory(
+            sourcepackagename=ds_diff.source_package_name,
+            distroseries=ds_diff.derived_series,
+            status=PackagePublishingStatus.PENDING,
+            version='1.0')
+
+        was_updated = ds_diff.update()
+
+        self.assertTrue(was_updated)
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
+            ds_diff.status)
+
     def test_title(self):
         # The title is a friendly description of the difference.
         parent_series = self.factory.makeDistroSeries(name="lucid")
@@ -287,6 +337,93 @@
             diff_comment = ds_diff.addComment(
                 ds_diff.derived_series.owner, "Boo")
 
+    def test_blacklist_not_public(self):
+        # Differences cannot be blacklisted without edit access.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+        person = self.factory.makePerson()
+
+        with person_logged_in(person):
+            self.assertTrue(check_permission('launchpad.View', ds_diff))
+            self.assertFalse(check_permission('launchpad.Edit', ds_diff))
+            self.assertRaises(Unauthorized, getattr, ds_diff, 'blacklist')
+
+    def test_blacklist_default(self):
+        # By default the current version is blacklisted.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+
+        with person_logged_in(ds_diff.owner):
+            ds_diff.blacklist()
+
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
+            ds_diff.status)
+
+    def test_blacklist_all(self):
+        # All versions are blacklisted with the all=True param.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+
+        with person_logged_in(ds_diff.owner):
+            ds_diff.blacklist(all=True)
+
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
+            ds_diff.status)
+
+    def test_unblacklist(self):
+        # Unblacklisting will return to NEEDS_ATTENTION by default.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
+
+        with person_logged_in(ds_diff.owner):
+            ds_diff.unblacklist()
+
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
+            ds_diff.status)
+
+    def test_unblacklist_resolved(self):
+        # Status is resolved when unblacklisting a now-resolved difference.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            versions={
+                'derived': '0.9',
+                'parent': '1.0',
+                },
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS)
+        new_derived_pub = self.factory.makeSourcePackagePublishingHistory(
+            sourcepackagename=ds_diff.source_package_name,
+            distroseries=ds_diff.derived_series,
+            status=PackagePublishingStatus.PENDING,
+            version='1.0')
+
+        with person_logged_in(ds_diff.owner):
+            ds_diff.unblacklist()
+
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.RESOLVED,
+            ds_diff.status)
+
+    def test_source_package_name_unique_for_derived_series(self):
+        # We cannot create two differences for the same derived series
+        # for the same package.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            source_package_name_str="foo")
+        self.assertRaises(
+            IntegrityError, self.factory.makeDistroSeriesDifference,
+            derived_series=ds_diff.derived_series,
+            source_package_name_str="foo")
+
+    def test_cached_properties(self):
+        # The source and parent publication properties are cached on the
+        # model.
+        ds_diff = self.factory.makeDistroSeriesDifference()
+        ds_diff.source_pub
+        ds_diff.parent_source_pub
+
+        cache = IPropertyCacheManager(ds_diff).cache
+
+        self.assertContentEqual(
+            ['source_pub', 'parent_source_pub'], cache)
+
 
 class DistroSeriesDifferenceSourceTestCase(TestCaseWithFactory):
 
@@ -317,7 +454,7 @@
         diffs['ignored'].append(
             self.factory.makeDistroSeriesDifference(
                 derived_series=derived_series,
-                status=DistroSeriesDifferenceStatus.IGNORED))
+                status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT))
         return diffs
 
     def makeDerivedSeries(self):
@@ -364,7 +501,7 @@
 
         result = getUtility(IDistroSeriesDifferenceSource).getForDistroSeries(
             derived_series,
-            status=DistroSeriesDifferenceStatus.IGNORED)
+            status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
 
         self.assertContentEqual(diffs['ignored'], result)
 
@@ -376,12 +513,23 @@
         result = getUtility(IDistroSeriesDifferenceSource).getForDistroSeries(
             derived_series,
             status=(
-                DistroSeriesDifferenceStatus.IGNORED,
+                DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
                 DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
                 ))
 
         self.assertContentEqual(diffs['normal'] + diffs['ignored'], result)
 
+    def test_getByDistroSeriesAndName(self):
+        # An individual difference is obtained using the name.
+        ds_diff = self.factory.makeDistroSeriesDifference(
+            source_package_name_str='fooname')
+
+        dsd_source = getUtility(IDistroSeriesDifferenceSource)
+        result = dsd_source.getByDistroSeriesAndName(
+            ds_diff.derived_series, 'fooname')
+
+        self.assertEqual(ds_diff, result)
+
 
 def test_suite():
     return unittest.TestLoader().loadTestsFromName(__name__)

=== modified file 'lib/lp/registry/tests/test_distroseriesdifferencecomment.py'
--- lib/lp/registry/tests/test_distroseriesdifferencecomment.py	2010-08-31 15:56:29 +0000
+++ lib/lp/registry/tests/test_distroseriesdifferencecomment.py	2010-09-29 09:44:52 +0000
@@ -29,12 +29,12 @@
 
         verifyObject(IDistroSeriesDifferenceComment, dsd_comment)
 
-    def test_comment(self):
+    def test_body_text(self):
         # The comment attribute returns the text of the comment.
         dsd_comment = self.factory.makeDistroSeriesDifferenceComment(
             comment="Wait until version 2.3")
 
-        self.assertEqual("Wait until version 2.3", dsd_comment.comment)
+        self.assertEqual("Wait until version 2.3", dsd_comment.body_text)
 
     def test_subject(self):
         # The subject of the message is set from the distro series diff
@@ -44,3 +44,27 @@
         self.assertEqual(
             dsd_comment.distro_series_difference.title,
             dsd_comment.message.subject)
+
+    def test_comment_author(self):
+        # The comment author just proxies the author from the message.
+        dsd_comment = self.factory.makeDistroSeriesDifferenceComment()
+
+        self.assertEqual(dsd_comment.message.owner, dsd_comment.comment_author)
+
+    def test_comment_date(self):
+        # The comment date attribute just proxies from the message.
+        dsd_comment = self.factory.makeDistroSeriesDifferenceComment()
+
+        self.assertEqual(
+            dsd_comment.message.datecreated, dsd_comment.comment_date)
+
+    def test_getForDifference(self):
+        # The utility can get comments by id.
+        dsd_comment = self.factory.makeDistroSeriesDifferenceComment()
+        Store.of(dsd_comment).flush()
+
+        comment_src = getUtility(IDistroSeriesDifferenceCommentSource)
+        self.assertEqual(
+            dsd_comment, comment_src.getForDifference(
+                dsd_comment.distro_series_difference, dsd_comment.id))
+

=== added file 'lib/lp/registry/windmill/tests/test_distroseriesdifference_expander.py'
--- lib/lp/registry/windmill/tests/test_distroseriesdifference_expander.py	1970-01-01 00:00:00 +0000
+++ lib/lp/registry/windmill/tests/test_distroseriesdifference_expander.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,72 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+from zope.component import getUtility
+
+import transaction
+
+from canonical.launchpad.webapp.publisher import canonical_url
+from canonical.launchpad.windmill.testing import constants
+from canonical.launchpad.windmill.testing import lpuser
+from lp.registry.enum import DistroSeriesDifferenceStatus
+from lp.registry.interfaces.distroseriesdifference import IDistroSeriesDifferenceSource
+from lp.registry.windmill.testing import RegistryWindmillLayer
+from lp.services.features.model import FeatureFlag, getFeatureStore
+from lp.testing import WindmillTestCase
+
+
+class TestDistroSeriesDifferenceExtraJS(WindmillTestCase):
+    """Each listed source package can be expanded for extra information."""
+
+    layer = RegistryWindmillLayer
+
+    def setUp(self):
+        """Enable the feature and populate with data."""
+        super(TestDistroSeriesDifferenceExtraJS, self).setUp()
+        # First just ensure that the feature is enabled.
+        getFeatureStore().add(FeatureFlag(
+            scope=u'default', flag=u'soyuz.derived-series-ui.enabled',
+            value=u'on', priority=1))
+
+        # Setup the difference record.
+        self.diff = self.factory.makeDistroSeriesDifference(
+            source_package_name_str="foo", versions=dict(
+                derived='1.15-2ubuntu1derilucid2', parent='1.17-1'))
+        transaction.commit()
+
+        self.package_diffs_url = (
+            canonical_url(self.diff.derived_series) + '/+localpackagediffs')
+
+    def test_diff_extra_details_blacklisting(self):
+        """A successful request for the extra info updates the display."""
+        #login_person(self.diff.owner, 'test', self.client)
+        lpuser.FOO_BAR.ensure_login(self.client)
+        self.client.open(url=self.package_diffs_url)
+        self.client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
+        self.client.click(link=u'foo')
+        self.client.waits.forElement(
+            classname=u'diff-extra', timeout=constants.FOR_ELEMENT)
+
+        self.client.click(id=u'field.blacklist_options.1')
+        self.client.waits.forElementProperty(
+            option=u'enabled', id=u'field.blacklist_options.1')
+
+        # Reload the diff and ensure it's been updated.
+        transaction.commit()
+        diff_source = getUtility(IDistroSeriesDifferenceSource)
+        diff_reloaded = diff_source.getByDistroSeriesAndName(
+            self.diff.derived_series, 'foo')
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
+            diff_reloaded.status)
+
+        # Now set it back so that it's not blacklisted.
+        self.client.click(id=u'field.blacklist_options.0')
+        self.client.waits.forElementProperty(
+            option=u'enabled', id=u'field.blacklist_options.0')
+        transaction.commit()
+        diff_reloaded = diff_source.getByDistroSeriesAndName(
+            self.diff.derived_series, 'foo')
+        self.assertEqual(
+            DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
+            diff_reloaded.status)

=== modified file 'lib/lp/services/comments/browser/configure.zcml'
--- lib/lp/services/comments/browser/configure.zcml	2009-07-17 02:25:09 +0000
+++ lib/lp/services/comments/browser/configure.zcml	2010-09-29 09:44:52 +0000
@@ -15,10 +15,18 @@
       permission="zope.Public"
       template="../templates/conversation.pt"/>
 
-  <browser:page
-      name="+render"
+  <browser:pages
       for="lp.services.comments.interfaces.conversation.IComment"
-      permission="zope.Public"
-      template="../templates/comment.pt"/>
+      permission="zope.Public">
+      <browser:page
+          name="+render"
+          template="../templates/comment.pt"/>
+      <browser:page
+          name="+comment-header"
+          template="../templates/comment-header.pt"/>
+      <browser:page
+          name="+comment-body"
+          template="../templates/comment-body.pt"/>
+  </browser:pages>
 
 </configure>

=== modified file 'lib/lp/services/comments/interfaces/conversation.py'
--- lib/lp/services/comments/interfaces/conversation.py	2010-08-20 20:31:18 +0000
+++ lib/lp/services/comments/interfaces/conversation.py	2010-09-29 09:44:52 +0000
@@ -17,6 +17,8 @@
 from zope.interface import Interface
 from zope.schema import (
     Bool,
+    Datetime,
+    Text,
     TextLine,
     )
 
@@ -37,6 +39,22 @@
         description=_("Does the comment have a footer?"),
         readonly=True)
 
+    body_text = Text(
+        description=_("The body text of the comment."),
+        readonly=True)
+
+    comment_author = Reference(
+        # Really IPerson.
+        Interface, title=_("The author of the comment."),
+        readonly=True)
+
+    comment_date = Datetime(
+        title=_('Comment date.'), readonly=True)
+
+    display_attachments = Bool(
+        description=_("Should attachments be displayed for this comment."),
+        readonly=True)
+
 
 class IConversation(Interface):
     """A conversation has a number of comments."""

=== added file 'lib/lp/services/comments/templates/comment-body.pt'
--- lib/lp/services/comments/templates/comment-body.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/services/comments/templates/comment-body.pt	2010-09-29 09:44:52 +0000
@@ -0,0 +1,8 @@
+<tal:root
+   xmlns:tal="http://xml.zope.org/namespaces/tal";
+   xmlns:metal="http://xml.zope.org/namespaces/metal";
+   omit-tag="">
+
+  <tal:message replace="structure context/body_text/fmt:obfuscate-email/fmt:nice_pre" />
+
+</tal:root>

=== added file 'lib/lp/services/comments/templates/comment-header.pt'
--- lib/lp/services/comments/templates/comment-header.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/services/comments/templates/comment-header.pt	2010-09-29 09:44:52 +0000
@@ -0,0 +1,9 @@
+<tal:root
+   xmlns:tal="http://xml.zope.org/namespaces/tal";
+   xmlns:metal="http://xml.zope.org/namespaces/metal";
+   omit-tag="">
+
+  <tal:author replace="structure context/comment_author/fmt:link:mainsite"/>
+  <tal:has-body condition="context/has_body">wrote</tal:has-body>
+  <tal:date replace="context/comment_date/fmt:displaydate" />
+</tal:root>

=== modified file 'lib/lp/soyuz/browser/configure.zcml'
--- lib/lp/soyuz/browser/configure.zcml	2010-07-02 14:34:58 +0000
+++ lib/lp/soyuz/browser/configure.zcml	2010-09-29 09:44:52 +0000
@@ -831,13 +831,6 @@
         permission="launchpad.View"
         template="../templates/buildqueue-current.pt"/>
     <browser:page
-        for="lp.buildmaster.interfaces.buildqueue.IBuildFarmJob"
-        name="+current"
-        class="canonical.launchpad.webapp.publisher.LaunchpadView"
-        facet="overview"
-        permission="launchpad.View"
-        template="../templates/buildfarmjob-current.pt"/>
-    <browser:page
         for="lp.buildmaster.interfaces.buildfarmbranchjob.IBuildFarmBranchJob"
         name="+current"
         class="canonical.launchpad.webapp.publisher.LaunchpadView"

=== modified file 'lib/lp/soyuz/browser/distroarchseries.py'
--- lib/lp/soyuz/browser/distroarchseries.py	2010-08-20 20:31:18 +0000
+++ lib/lp/soyuz/browser/distroarchseries.py	2010-09-29 09:44:52 +0000
@@ -124,7 +124,8 @@
     schema = IDistroArchSeries
 
     field_names = [
-        'architecturetag', 'official', 'supports_virtualized'
+        'architecturetag', 'official', 'supports_virtualized',
+        'enabled',
         ]
 
     @action(_('Change'), name='update')

=== added file 'lib/lp/soyuz/browser/tests/test_distroarchseries_view.py'
--- lib/lp/soyuz/browser/tests/test_distroarchseries_view.py	1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/browser/tests/test_distroarchseries_view.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,51 @@
+# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+from canonical.launchpad.ftests import login
+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+from canonical.testing import LaunchpadFunctionalLayer
+from lp.soyuz.browser.distroarchseries import DistroArchSeriesAdminView
+from lp.testing import TestCaseWithFactory
+from lp.testing.sampledata import LAUNCHPAD_ADMIN
+
+
+class TestDistroArchSeriesView(TestCaseWithFactory):
+
+    layer = LaunchpadFunctionalLayer
+
+    def setUp(self):
+        """Create a distroarchseries for the tests and login as an admin."""
+        super(TestDistroArchSeriesView, self).setUp()
+        self.das = self.factory.makeDistroArchSeries()
+        # Login as an admin to ensure access to the view's context
+        # object.
+        login(LAUNCHPAD_ADMIN)
+
+    def initialize_admin_view(self, enabled=True):
+        # Initialize the admin view with the supplied params.
+        method = 'POST'
+        form = {
+            'field.actions.update': 'update',
+            }
+
+        if enabled:
+            form['field.enabled'] = 'on'
+        else:
+            form['field.enabled'] = 'off'
+
+        view = DistroArchSeriesAdminView(
+            self.das, LaunchpadTestRequest(method=method, form=form))
+        view.initialize()
+        return view
+
+    def test_enabling_enabled_flag(self):
+        view = self.initialize_admin_view(enabled=False)
+        self.assertEqual(0, len(view.errors))
+        self.assertFalse(view.context.enabled)
+
+    def test_disabling_enabled_flag(self):
+        view = self.initialize_admin_view(enabled=True)
+        self.assertEqual(0, len(view.errors))
+        self.assertTrue(view.context.enabled)

=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml	2010-08-20 13:47:47 +0000
+++ lib/lp/soyuz/configure.zcml	2010-09-29 09:44:52 +0000
@@ -878,4 +878,14 @@
       callable="lp.archiveuploader.tests.register_archive_upload_policy_adapters"
       />
 
+    <!-- InitialiseDistroSeriesJobSource -->
+    <securedutility
+      component="lp.soyuz.model.initialisedistroseriesjob.InitialiseDistroSeriesJob"
+      provides="lp.soyuz.interfaces.distributionjob.IInitialiseDistroSeriesJobSource">
+        <allow interface="lp.soyuz.interfaces.distributionjob.IInitialiseDistroSeriesJobSource"/>
+    </securedutility>
+    <class class="lp.soyuz.model.initialisedistroseriesjob.InitialiseDistroSeriesJob">
+        <allow interface="lp.services.job.interfaces.job.IRunnableJob" />
+    </class>
+
 </configure>

=== modified file 'lib/lp/soyuz/doc/distroarchseries.txt'
--- lib/lp/soyuz/doc/distroarchseries.txt	2010-07-20 09:36:17 +0000
+++ lib/lp/soyuz/doc/distroarchseries.txt	2010-09-29 09:44:52 +0000
@@ -25,6 +25,16 @@
 #      This needs many more tests to be effective.
 
 
+Properties
+==========
+
+Enabled is a boolean flag that says whether the arch will receive new builds
+and publish them.
+
+    >>> print hoary_i386.enabled
+    True
+
+
 DistroArchSeries can tell you about their published releases
 ============================================================
 

=== modified file 'lib/lp/soyuz/doc/package-arch-specific.txt'
--- lib/lp/soyuz/doc/package-arch-specific.txt	2010-08-24 15:29:01 +0000
+++ lib/lp/soyuz/doc/package-arch-specific.txt	2010-09-29 09:44:52 +0000
@@ -121,6 +121,17 @@
     >>> print_build_architectures(pub_one)
     hppa
 
+If an architecture is disabled for some reason, then the results from
+determineArchitecturesToBuild() will not include it.
+
+    >>> hoary['hppa'].enabled = False
+
+    >>> print_build_architectures(pub_three)
+    i386
+
+Re-enable it before continuing:
+    >>> hoary['hppa'].enabled = True
+
 
 == Check support for kernel notation in architecture hint list ==
 

=== added file 'lib/lp/soyuz/interfaces/distributionjob.py'
--- lib/lp/soyuz/interfaces/distributionjob.py	1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/interfaces/distributionjob.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,66 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+__all__ = [
+    "DistributionJobType",
+    "IDistributionJob",
+    "IInitialiseDistroSeriesJob",
+    "IInitialiseDistroSeriesJobSource",
+]
+
+from lazr.enum import DBEnumeratedType, DBItem
+from zope.interface import Attribute, Interface
+from zope.schema import Int, Object
+
+from canonical.launchpad import _
+
+from lp.services.job.interfaces.job import IJob, IJobSource, IRunnableJob
+from lp.registry.interfaces.distribution import IDistribution
+from lp.registry.interfaces.distroseries import IDistroSeries
+
+
+class IDistributionJob(Interface):
+    """A Job that initialises acts on a distribution."""
+
+    id = Int(
+        title=_('DB ID'), required=True, readonly=True,
+        description=_("The tracking number for this job."))
+
+    distribution = Object(
+        title=_('The Distribution this job is about.'),
+        schema=IDistribution, required=True)
+
+    distroseries = Object(
+        title=_('The DistroSeries this job is about.'),
+        schema=IDistroSeries, required=False)
+
+    job = Object(
+        title=_('The common Job attributes'), schema=IJob, required=True)
+
+    metadata = Attribute('A dict of data about the job.')
+
+    def destroySelf():
+        """Destroy this object."""
+
+
+class DistributionJobType(DBEnumeratedType):
+
+    INITIALISE_SERIES = DBItem(1, """
+        Initialise a Distro Series.
+
+        This job initialises a given distro series, creating builds, and
+        populating the archive from the parent distroseries.
+        """)
+
+
+class IInitialiseDistroSeriesJobSource(IJobSource):
+    """An interface for acquiring IDistributionJobs."""
+
+    def create(distroseries):
+        """Create a new initialisation job for a distroseries."""
+
+
+class IInitialiseDistroSeriesJob(IRunnableJob):
+    """A Job that performs actions on a distribution."""

=== modified file 'lib/lp/soyuz/interfaces/distroarchseries.py'
--- lib/lp/soyuz/interfaces/distroarchseries.py	2010-08-20 20:31:18 +0000
+++ lib/lp/soyuz/interfaces/distroarchseries.py	2010-09-29 09:44:52 +0000
@@ -83,6 +83,12 @@
             description=_("Indicate whether or not this port has support "
                           "for building PPA packages."),
             required=False))
+    enabled = Bool(
+        title=_("Enabled"),
+        description=_(
+            "Whether or not this DistroArchSeries is enabled for build "
+            "creation and publication."),
+        required=False, readonly=False)
 
     # Joins.
     packages = Attribute('List of binary packages in this port.')

=== added file 'lib/lp/soyuz/model/distributionjob.py'
--- lib/lp/soyuz/model/distributionjob.py	1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/model/distributionjob.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,112 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+__all__ = [
+    "DistributionJob",
+    "DistributionJobDerived",
+]
+
+import simplejson
+
+from storm.base import Storm
+from storm.locals import And, Int, Reference, Unicode
+
+from zope.interface import implements
+
+from canonical.database.enumcol import EnumCol
+from canonical.launchpad.interfaces.lpstorm import IStore
+
+from lazr.delegates import delegates
+
+from lp.app.errors import NotFoundError
+from lp.registry.model.distribution import Distribution
+from lp.registry.model.distroseries import DistroSeries
+from lp.soyuz.interfaces.distributionjob import (
+    DistributionJobType,
+    IDistributionJob,
+    )
+from lp.services.job.model.job import Job
+from lp.services.job.runner import BaseRunnableJob
+
+
+class DistributionJob(Storm):
+    """Base class for jobs related to Distributions."""
+
+    implements(IDistributionJob)
+
+    __storm_table__ = 'DistributionJob'
+
+    id = Int(primary=True)
+
+    job_id = Int(name='job')
+    job = Reference(job_id, Job.id)
+
+    distribution_id = Int(name='distribution')
+    distribution = Reference(distribution_id, Distribution.id)
+
+    distroseries_id = Int(name='distroseries')
+    distroseries = Reference(distroseries_id, DistroSeries.id)
+
+    job_type = EnumCol(enum=DistributionJobType, notNull=True)
+
+    _json_data = Unicode('json_data')
+
+    def __init__(self, distribution, distroseries, job_type, metadata):
+        super(DistributionJob, self).__init__()
+        json_data = simplejson.dumps(metadata)
+        self.job = Job()
+        self.distribution = distribution
+        self.distroseries = distroseries
+        self.job_type = job_type
+        self._json_data = json_data.decode('utf-8')
+
+    @property
+    def metadata(self):
+        return simplejson.loads(self._json_data)
+
+
+class DistributionJobDerived(BaseRunnableJob):
+    """Abstract class for deriving from DistributionJob."""
+    delegates(IDistributionJob)
+
+    def __init__(self, job):
+        self.context = job
+
+    @classmethod
+    def get(cls, job_id):
+        """Get a job by id.
+
+        :return: the DistributionJob with the specified id, as
+                 the current DistributionJobDerived subclass.
+        :raises: NotFoundError if there is no job with the specified id,
+                 or its job_type does not match the desired subclass.
+        """
+        job = DistributionJob.get(job_id)
+        if job.job_type != cls.class_job_type:
+            raise NotFoundError(
+                'No object found with id %d and type %s' % (job_id,
+                cls.class_job_type.title))
+        return cls(job)
+
+    @classmethod
+    def iterReady(cls):
+        """Iterate through all ready DistributionJobs."""
+        jobs = IStore(DistributionJob).find(
+            DistributionJob,
+            And(DistributionJob.job_type == cls.class_job_type,
+                DistributionJob.job == Job.id,
+                Job.id.is_in(Job.ready_jobs)))
+        return (cls(job) for job in jobs)
+
+    def getOopsVars(self):
+        """See `IRunnableJob`."""
+        vars = BaseRunnableJob.getOopsVars(self)
+        vars.extend([
+            ('distribution_id', self.context.distribution.id),
+            ('distroseries_id', self.context.distroseries.id),
+            ('distribution_job_id', self.context.id),
+            ('distribution_job_type', self.context.job_type.title),
+            ])
+        return vars

=== modified file 'lib/lp/soyuz/model/distroarchseries.py'
--- lib/lp/soyuz/model/distroarchseries.py	2010-08-24 15:29:01 +0000
+++ lib/lp/soyuz/model/distroarchseries.py	2010-09-29 09:44:52 +0000
@@ -82,6 +82,7 @@
         storm_validator=validate_public_person, notNull=True)
     package_count = IntCol(notNull=True, default=DEFAULT)
     supports_virtualized = BoolCol(notNull=False, default=False)
+    enabled = BoolCol(notNull=False, default=True)
 
     packages = SQLRelatedJoin('BinaryPackageRelease',
         joinColumn='distroarchseries',
@@ -337,6 +338,11 @@
 
     def publish(self, diskpool, log, archive, pocket, is_careful=False):
         """See `ICanPublishPackages`."""
+        if not self.enabled:
+            log.debug(
+                "Skipping disabled architecture %s" % self.architecturetag)
+            return set()
+
         log.debug("Attempting to publish pending binaries for %s"
               % self.architecturetag)
 

=== added file 'lib/lp/soyuz/model/initialisedistroseriesjob.py'
--- lib/lp/soyuz/model/initialisedistroseriesjob.py	1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/model/initialisedistroseriesjob.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,48 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+__all__ = [
+    "InitialiseDistroSeriesJob",
+]
+
+from zope.interface import classProvides, implements
+
+from canonical.launchpad.interfaces.lpstorm import IMasterStore
+
+from lp.soyuz.interfaces.distributionjob import (
+    DistributionJobType,
+    IInitialiseDistroSeriesJob,
+    IInitialiseDistroSeriesJobSource,
+    )
+from lp.soyuz.model.distributionjob import (
+    DistributionJob,
+    DistributionJobDerived,
+    )
+from lp.soyuz.scripts.initialise_distroseries import (
+    InitialiseDistroSeries,
+    )
+
+
+class InitialiseDistroSeriesJob(DistributionJobDerived):
+
+    implements(IInitialiseDistroSeriesJob)
+
+    class_job_type = DistributionJobType.INITIALISE_SERIES
+    classProvides(IInitialiseDistroSeriesJobSource)
+
+    @classmethod
+    def create(cls, distroseries):
+        """See `IInitialiseDistroSeriesJob`."""
+        job = DistributionJob(
+            distroseries.distribution, distroseries, cls.class_job_type,
+            ())
+        IMasterStore(DistributionJob).add(job)
+        return cls(job)
+
+    def run(self):
+        """See `IRunnableJob`."""
+        ids = InitialiseDistroSeries(self.distroseries)
+        ids.check()
+        ids.initialise()

=== modified file 'lib/lp/soyuz/pas.py'
--- lib/lp/soyuz/pas.py	2010-08-23 16:51:11 +0000
+++ lib/lp/soyuz/pas.py	2010-09-29 09:44:52 +0000
@@ -164,7 +164,8 @@
         if not legal_archseries:
             return []
 
-    legal_arch_tags = set(arch.architecturetag for arch in legal_archseries)
+    legal_arch_tags = set(
+        arch.architecturetag for arch in legal_archseries if arch.enabled)
 
     # We need to support arch tags like any-foo and linux-foo, so remove
     # supported kernel prefixes, Also allow linux-any but not any-any.

=== added file 'lib/lp/soyuz/tests/test_initialisedistroseriesjob.py'
--- lib/lp/soyuz/tests/test_initialisedistroseriesjob.py	1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/tests/test_initialisedistroseriesjob.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,74 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+import transaction
+from canonical.testing import LaunchpadZopelessLayer
+from storm.exceptions import IntegrityError
+from zope.component import getUtility
+from zope.security.proxy import removeSecurityProxy
+from lp.soyuz.interfaces.distributionjob import (
+    IInitialiseDistroSeriesJobSource,
+    )
+from lp.soyuz.model.initialisedistroseriesjob import (
+    InitialiseDistroSeriesJob,
+    )
+from lp.soyuz.scripts.initialise_distroseries import InitialisationError
+from lp.testing import TestCaseWithFactory
+
+
+class InitialiseDistroSeriesJobTests(TestCaseWithFactory):
+    """Test case for InitialiseDistroSeriesJob."""
+
+    layer = LaunchpadZopelessLayer
+
+    def test_getOopsVars(self):
+        distroseries = self.factory.makeDistroSeries()
+        job = getUtility(IInitialiseDistroSeriesJobSource).create(
+            distroseries)
+        vars = job.getOopsVars()
+        naked_job = removeSecurityProxy(job)
+        self.assertIn(
+            ('distribution_id', distroseries.distribution.id), vars)
+        self.assertIn(('distroseries_id', distroseries.id), vars)
+        self.assertIn(('distribution_job_id', naked_job.context.id), vars)
+
+    def _getJobs(self):
+        """Return the pending InitialiseDistroSeriesJobs as a list."""
+        return list(InitialiseDistroSeriesJob.iterReady())
+
+    def _getJobCount(self):
+        """Return the number of InitialiseDistroSeriesJobs in the
+        queue."""
+        return len(self._getJobs())
+
+    def test_create_only_creates_one(self):
+        distroseries = self.factory.makeDistroSeries()
+        # If there's already a InitialiseDistroSeriesJob for a
+        # DistroSeries, InitialiseDistroSeriesJob.create() won't create
+        # a new one.
+        job = getUtility(IInitialiseDistroSeriesJobSource).create(
+            distroseries)
+        transaction.commit()
+
+        # There will now be one job in the queue.
+        self.assertEqual(1, self._getJobCount())
+
+        new_job = getUtility(IInitialiseDistroSeriesJobSource).create(
+            distroseries)
+
+        # This is less than ideal
+        self.assertRaises(IntegrityError, self._getJobCount)
+
+    def test_run(self):
+        """Test that InitialiseDistroSeriesJob.run() actually
+        initialises builds and copies from the parent."""
+        distroseries = self.factory.makeDistroSeries()
+
+        job = getUtility(IInitialiseDistroSeriesJobSource).create(
+            distroseries)
+
+        # Since our new distroseries doesn't have a parent set, and the first
+        # thing that run() will execute is checking the distroseries, if it
+        # returns an InitialisationError, then it's good.
+        self.assertRaisesWithContent(
+            InitialisationError, "Parent series required.", job.run)

=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py	2010-08-30 19:06:34 +0000
+++ lib/lp/soyuz/tests/test_publishing.py	2010-09-29 09:44:52 +0000
@@ -494,45 +494,47 @@
 
         return source
 
-    def makeSourcePackageWithBinaryPackageRelease(self):
+    def makeSourcePackageSummaryData(self, source_pub=None):
         """Make test data for SourcePackage.summary.
 
         The distroseries that is returned from this method needs to be
         passed into updateDistroseriesPackageCache() so that
         SourcePackage.summary can be populated.
         """
-        distribution = self.factory.makeDistribution(
-            name='youbuntu', displayname='Youbuntu')
-        distroseries = self.factory.makeDistroRelease(name='busy',
-            distribution=distribution)
-        source_package_name = self.factory.makeSourcePackageName(
-            name='bonkers')
-        source_package = self.factory.makeSourcePackage(
-            sourcepackagename=source_package_name,
-            distroseries=distroseries)
-        component = self.factory.makeComponent('multiverse')
+        if source_pub is None:
+            distribution = self.factory.makeDistribution(
+                name='youbuntu', displayname='Youbuntu')
+            distroseries = self.factory.makeDistroRelease(name='busy',
+                distribution=distribution)
+            source_package_name = self.factory.makeSourcePackageName(
+                name='bonkers')
+            source_package = self.factory.makeSourcePackage(
+                sourcepackagename=source_package_name,
+                distroseries=distroseries)
+            component = self.factory.makeComponent('multiverse')
+            source_pub = self.factory.makeSourcePackagePublishingHistory(
+                sourcepackagename=source_package_name,
+                distroseries=distroseries,
+                component=component)
+
         das = self.factory.makeDistroArchSeries(
-            distroseries=distroseries)
-        spph = self.factory.makeSourcePackagePublishingHistory(
-            sourcepackagename=source_package_name,
-            distroseries=distroseries,
-            component=component)
+            distroseries=source_pub.distroseries)
 
         for name in ('flubber-bin', 'flubber-lib'):
             binary_package_name = self.factory.makeBinaryPackageName(name)
             build = self.factory.makeBinaryPackageBuild(
-                source_package_release=spph.sourcepackagerelease,
+                source_package_release=source_pub.sourcepackagerelease,
                 archive=self.factory.makeArchive(),
                 distroarchseries=das)
             bpr = self.factory.makeBinaryPackageRelease(
                 binarypackagename=binary_package_name,
                 summary='summary for %s' % name,
-                build=build, component=component)
+                build=build, component=source_pub.component)
             bpph = self.factory.makeBinaryPackagePublishingHistory(
                 binarypackagerelease=bpr, distroarchseries=das)
         return dict(
-            distroseries=distroseries,
-            source_package=source_package)
+            distroseries=source_pub.distroseries,
+            source_package=source_pub.meta_sourcepackage)
 
     def updateDistroSeriesPackageCache(
         self, distroseries, restore_db_connection='launchpad'):

=== modified file 'lib/lp/soyuz/tests/test_publishing_top_level_api.py'
--- lib/lp/soyuz/tests/test_publishing_top_level_api.py	2010-08-24 15:29:01 +0000
+++ lib/lp/soyuz/tests/test_publishing_top_level_api.py	2010-09-29 09:44:52 +0000
@@ -419,3 +419,16 @@
         self.checkBinaryLookupForPocket(
             PackagePublishingPocket.RELEASE, is_careful=True,
             expected_result=[pub_published_release, pub_pending_release])
+
+    def test_publishing_disabled_distroarchseries(self):
+        # Disabled DASes will be skipped even if there are pending
+        # publications for them.
+        binaries = self.getPubBinaries(architecturespecific=True)
+        # Just use the first binary.
+        binary = binaries[0]
+        self.assertEqual(PackagePublishingStatus.PENDING, binary.status)
+
+        binary.distroarchseries.enabled = False
+        self._publish(pocket=binary.pocket)
+
+        self.assertEqual(PackagePublishingStatus.PENDING, binary.status)

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2010-09-27 15:01:11 +0000
+++ lib/lp/testing/factory.py	2010-09-29 09:44:52 +0000
@@ -215,6 +215,7 @@
 from lp.registry.model.suitesourcepackage import SuiteSourcePackage
 from lp.services.mail.signedmessage import SignedMessage
 from lp.services.openid.model.openididentifier import OpenIdIdentifier
+from lp.services.propertycache import IPropertyCacheManager
 from lp.services.worlddata.interfaces.country import ICountrySet
 from lp.services.worlddata.interfaces.language import ILanguageSet
 from lp.soyuz.adapters.packagelocation import PackageLocation
@@ -250,8 +251,10 @@
 from lp.soyuz.model.processor import ProcessorFamilySet
 from lp.testing import (
     ANONYMOUS,
+    launchpadlib_for,
     login,
     login_as,
+    login_person,
     person_logged_in,
     run_with_login,
     temp_dir,
@@ -1815,6 +1818,22 @@
             expires=expires, restricted=restricted)
         return library_file_alias
 
+    def makePackageDiff(self, from_spr=None, to_spr=None):
+        """Make a completed package diff."""
+        if from_spr is None:
+            from_spr = self.makeSourcePackageRelease()
+        if to_spr is None:
+            to_spr = self.makeSourcePackageRelease()
+
+        diff = from_spr.requestDiffTo(
+            from_spr.creator, to_spr)
+
+        naked_diff = removeSecurityProxy(diff)
+        naked_diff.status = PackageDiffStatus.COMPLETED
+        naked_diff.diff_content = self.makeLibraryFileAlias()
+        naked_diff.date_fulfilled = UTC_NOW
+        return diff
+
     def makeDistribution(self, name=None, displayname=None, owner=None,
                          members=None, title=None, aliases=None):
         """Make a new distribution."""
@@ -1905,10 +1924,15 @@
                 sourcepackagename=source_package_name,
                 status = PackagePublishingStatus.PUBLISHED)
 
-        return getUtility(IDistroSeriesDifferenceSource).new(
+        diff = getUtility(IDistroSeriesDifferenceSource).new(
             derived_series, source_package_name, difference_type,
             status=status)
 
+        # We clear the cache on the diff, returning the object as if it
+        # was just loaded from the store.
+        IPropertyCacheManager(diff).clear()
+        return diff
+
     def makeDistroSeriesDifferenceComment(
         self, distro_series_difference=None, owner=None, comment=None):
         """Create a new distro series difference comment."""
@@ -3095,6 +3119,7 @@
 
         return getUtility(ITemporaryStorageManager).fetch(new_uuid)
 
+<<<<<<< TREE
     def makePackageDiff(self, from_source=None, to_source=None,
                         requester=None, status=None, date_fulfilled=None,
                         diff_content=None):
@@ -3118,6 +3143,16 @@
                 to_source=to_source, date_fulfilled=date_fulfilled,
                 status=status, diff_content=lfa))
 
+=======
+    def makeLaunchpadService(self, person=None):
+        if person is None:
+            person = self.makePerson()
+        launchpad = launchpadlib_for("test", person,
+            service_root="http://api.launchpad.dev:8085";)
+        login_person(person)
+        return launchpad
+
+>>>>>>> MERGE-SOURCE
 
 # 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/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml	2010-09-23 05:00:58 +0000
+++ lib/lp/translations/browser/configure.zcml	2010-09-29 09:44:52 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2009 Canonical Ltd.  This software is licensed under the
+<!-- Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
      GNU Affero General Public License version 3 (see the file LICENSE).
 -->
 
@@ -1065,4 +1065,21 @@
         permission="launchpad.Admin"/>
 
     </facet>
+
+<!-- TranslationTemplateBuild -->
+    <browser:defaultView
+        for="lp.translations.interfaces.translationtemplatesbuild.ITranslationTemplatesBuild"
+        name="+index"/>
+    <browser:url
+        for="lp.translations.interfaces.translationtemplatesbuild.ITranslationTemplatesBuild"
+        path_expression="string:+translation-templates-build/${build_farm_job/id}"
+        attribute_to_parent="branch"/>
+    <browser:page
+        for="lp.translations.interfaces.translationtemplatesbuild.ITranslationTemplatesBuild"
+        class="lp.translations.browser.translationtemplatesbuild.TranslationTemplatesBuildView"
+        permission="launchpad.View"
+        name="+index"
+        facet="overview"
+        template="../templates/translationtemplatesbuild-index.pt"/>
+
 </configure>

=== added file 'lib/lp/translations/browser/tests/test_translationtemplatesbuild.py'
--- lib/lp/translations/browser/tests/test_translationtemplatesbuild.py	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/browser/tests/test_translationtemplatesbuild.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,84 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""`TranslationTemplatesBuild` view tests."""
+
+__metaclass__ = type
+
+from datetime import datetime
+from pytz import utc
+from zope.component import getUtility
+from zope.security.proxy import removeSecurityProxy
+
+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+from canonical.testing import DatabaseFunctionalLayer
+from lp.buildmaster.enums import BuildFarmJobType
+from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
+from lp.testing import TestCaseWithFactory
+from lp.translations.browser.translationtemplatesbuild import (
+    TranslationTemplatesBuildView,
+    )
+from lp.translations.interfaces.translations import (
+    TranslationsBranchImportMode,
+    )
+from lp.translations.interfaces.translationtemplatesbuild import (
+    ITranslationTemplatesBuildSource,
+    )
+
+
+def now():
+    """Now."""
+    return datetime.now(utc)
+
+
+class TestTranslationTemplatesBuild(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def _makeBuild(self, branch=None):
+        """Create a `TranslationTemplatesBuild`."""
+        if branch is None:
+            branch = self.factory.makeBranch()
+        job = getUtility(IBuildFarmJobSource).new(
+            BuildFarmJobType.TRANSLATIONTEMPLATESBUILD)
+        return getUtility(ITranslationTemplatesBuildSource).create(
+            job, branch)
+
+    def _makeView(self, build=None):
+        """Create a view for testing."""
+        if build is None:
+            build = self._makeBuild()
+        request = LaunchpadTestRequest()
+        view = TranslationTemplatesBuildView(build, request)
+        view.initialize()
+        return view
+
+    def _makeProductSeries(self, branch):
+        """Create a `ProductSeries` that imports templates from `branch`."""
+        productseries = self.factory.makeProductSeries()
+        removeSecurityProxy(productseries).branch = branch
+        removeSecurityProxy(productseries).translations_autoimport_mode = (
+            TranslationsBranchImportMode.IMPORT_TEMPLATES)
+        return productseries
+
+    def test_getTargets_finds_target(self):
+        productseries = self._makeProductSeries(self.factory.makeBranch())
+        view = self._makeView(self._makeBuild(productseries.branch))
+        self.assertContentEqual([productseries], view.getTargets())
+
+    def test_renderDispatchTime(self):
+        build = self._makeBuild()
+        view = self._makeView(build)
+        self.assertEqual("Not started yet.", view.renderDispatchTime())
+        removeSecurityProxy(build.build_farm_job).date_started = now()
+        self.assertIn("Started", view.renderDispatchTime())
+
+    def test_renderFinishTime(self):
+        """Finish time is shown once build has started."""
+        build = self._makeBuild()
+        view = self._makeView(build)
+        self.assertEqual("", view.renderFinishTime())
+        removeSecurityProxy(build.build_farm_job).date_started = now()
+        self.assertEqual("Not finished yet.", view.renderFinishTime())
+        removeSecurityProxy(build.build_farm_job).date_finished = now()
+        self.assertIn("Finished", view.renderFinishTime())

=== added file 'lib/lp/translations/browser/translationtemplatesbuild.py'
--- lib/lp/translations/browser/translationtemplatesbuild.py	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/browser/translationtemplatesbuild.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,63 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Display `TranslationTemplateBuild`s."""
+
+__metaclass__ = type
+__all__ = [
+    'TranslationTemplatesBuildNavigation',
+    'TranslationTemplatesBuildUrl',
+    'TranslationTemplatesBuildView',
+    ]
+
+from zope.component import getUtility
+
+from canonical.launchpad.webapp.publisher import LaunchpadView
+from canonical.launchpad.webapp.tales import DateTimeFormatterAPI
+from lp.registry.interfaces.productseries import IProductSeriesSet
+from lp.translations.model.translationtemplatesbuildjob import (
+    HARDCODED_TRANSLATIONTEMPLATESBUILD_SCORE,
+    )
+
+
+class TranslationTemplatesBuildView(LaunchpadView):
+    """View for `TranslationTemplatesBuild`."""
+
+    def getTargets(self):
+        """`ProducSeries` that will consume the generated templates."""
+        utility = getUtility(IProductSeriesSet)
+        return list(
+            utility.findByTranslationsImportBranch(self.context.branch))
+
+    def _renderTime(self, time):
+        """Represent `time` as HTML."""
+        formatter = DateTimeFormatterAPI(time)
+        return """<span title="%s">%s</span>""" % (
+            formatter.datetime(), formatter.approximatedate())
+
+    def initalize(self):
+        """See `LaunchpadView`."""
+        self.last_score = HARDCODED_TRANSLATIONTEMPLATESBUILD_SCORE
+
+    def renderDispatchTime(self):
+        """Give start-time information for this build, as HTML."""
+        # Once we do away with BuildQueue, and the relevant information
+        # is moved into the new model, we'll be able to give estimated
+        # start times as well.
+        if self.context.date_started is None:
+            return "Not started yet."
+        else:
+            return "Started " + self._renderTime(self.context.date_started)
+
+    def renderFinishTime(self):
+        """Give completion time information for this build, as HTML."""
+        # Once we do away with BuildQueue, and the relevant information
+        # is moved into the new model, we'll be able to give estimated
+        # completion times as well.
+        if self.context.date_finished is None:
+            if self.context.date_started is None:
+                return ''
+            else:
+                return "Not finished yet."
+        else:
+            return "Finished " + self._renderTime(self.context.date_finished)

=== modified file 'lib/lp/translations/configure.zcml'
--- lib/lp/translations/configure.zcml	2010-09-20 18:42:38 +0000
+++ lib/lp/translations/configure.zcml	2010-09-29 09:44:52 +0000
@@ -614,6 +614,23 @@
         component="lp.translations.model.translationtemplatesbuildjob.TranslationTemplatesBuildJob"
         provides="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJob"
         name="TRANSLATIONTEMPLATESBUILD"/>
+    <adapter
+        provides="lp.buildmaster.interfaces.buildfarmjob.ISpecificBuildFarmJob"
+        for="lp.buildmaster.interfaces.buildfarmjob.IBuildFarmJob"
+        factory="lp.translations.model.translationtemplatesbuild.get_translation_templates_build_for_build_farm_job"
+        name="TRANSLATIONTEMPLATESBUILD"
+        permission="zope.Public"/>
+
+    <!-- TranslationTemplatesBuild -->
+    <class
+        class="lp.translations.model.translationtemplatesbuild.TranslationTemplatesBuild">
+        <allow interface="lp.translations.interfaces.translationtemplatesbuild.ITranslationTemplatesBuild"/>
+    </class>
+    <securedutility
+        component="lp.translations.model.translationtemplatesbuild.TranslationTemplatesBuild"
+        provides="lp.translations.interfaces.translationtemplatesbuild.ITranslationTemplatesBuildSource">
+        <allow interface="lp.translations.interfaces.translationtemplatesbuild.ITranslationTemplatesBuildSource"/>
+    </securedutility>
 
     <!-- TranslationTemplateBuildBehavior -->
     <class

=== added file 'lib/lp/translations/interfaces/translationtemplatesbuild.py'
--- lib/lp/translations/interfaces/translationtemplatesbuild.py	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/interfaces/translationtemplatesbuild.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,49 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Interface and utility for `TranslationTemplatesBuild`."""
+
+__metaclass__ = type
+__all__ = [
+    'ITranslationTemplatesBuild',
+    'ITranslationTemplatesBuildSource',
+    ]
+
+from lazr.restful.fields import Reference
+from zope.interface import Interface
+
+from canonical.launchpad import _
+from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
+from lp.code.interfaces.branch import IBranch
+
+
+class ITranslationTemplatesBuild(IBuildFarmJob):
+    """The build information for translation templates builds."""
+
+    build_farm_job = Reference(
+        title=_("The build farm job that this extends."),
+        required=True, readonly=True, schema=IBuildFarmJob)
+
+    branch = Reference(
+        title=_("The branch that this build operates on."),
+        required=True, readonly=True, schema=IBranch)
+
+
+class ITranslationTemplatesBuildSource(Interface):
+    """Utility for `ITranslationTemplatesBuild`."""
+
+    def create(build_farm_job, branch):
+        """Create a new `ITranslationTemplatesBuild`."""
+
+    def findByBranch(branch, store=None):
+        """Find `ITranslationTemplatesBuild`s for `branch`."""
+
+    def get(build_id, store=None):
+        """Find `ITranslationTemplatesBuild`s by id.
+
+        :param build_id: Numerical id to look for.
+        :param store: Optional database store to look in.
+        """
+
+    def getByBuildFarmJob(buildfarmjob_id, store=None):
+        """Find `ITranslationTemplatesBuild`s by `BuildFarmJob` id."""

=== added file 'lib/lp/translations/model/translationtemplatesbuild.py'
--- lib/lp/translations/model/translationtemplatesbuild.py	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/model/translationtemplatesbuild.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,117 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""`TranslationTemplatesBuild` class."""
+
+__metaclass__ = type
+__all__ = [
+    'TranslationTemplatesBuild',
+    ]
+
+from storm.locals import (
+    Int,
+    Reference,
+    Storm,
+    )
+from storm.store import Store
+from zope.interface import (
+    classProvides,
+    implements,
+    )
+from zope.security.proxy import ProxyFactory
+
+from canonical.launchpad.interfaces.lpstorm import IStore
+from lp.buildmaster.model.buildfarmjob import BuildFarmJobDerived
+from lp.code.model.branchjob import (
+    BranchJob,
+    BranchJobType,
+    )
+from lp.translations.interfaces.translationtemplatesbuild import (
+    ITranslationTemplatesBuild,
+    ITranslationTemplatesBuildSource,
+    )
+from lp.translations.model.translationtemplatesbuildjob import (
+    TranslationTemplatesBuildJob,
+    )
+
+
+class TranslationTemplatesBuild(BuildFarmJobDerived, Storm):
+    """A `BuildFarmJob` extension for translation templates builds."""
+
+    implements(ITranslationTemplatesBuild)
+    classProvides(ITranslationTemplatesBuildSource)
+
+    __storm_table__ = 'TranslationTemplatesBuild'
+
+    id = Int(name='id', primary=True)
+    build_farm_job_id = Int(name='build_farm_job', allow_none=False)
+    build_farm_job = Reference(build_farm_job_id, 'BuildFarmJob.id')
+    branch_id = Int(name='branch', allow_none=False)
+    branch = Reference(branch_id, 'Branch.id')
+
+    def __init__(self, build_farm_job, branch):
+        super(TranslationTemplatesBuild, self).__init__()
+        self.build_farm_job = build_farm_job
+        self.branch = branch
+
+    def makeJob(self):
+        """See `IBuildFarmJobOld`."""
+        store = IStore(BranchJob)
+
+        # Pass public HTTP URL for the branch.
+        metadata = {'branch_url': self.branch.composePublicURL()}
+        branch_job = BranchJob(
+            self.branch, BranchJobType.TRANSLATION_TEMPLATES_BUILD, metadata)
+        store.add(branch_job)
+        return TranslationTemplatesBuildJob(branch_job)
+
+    @classmethod
+    def _getStore(cls, store=None):
+        """Return `store` if given, or the default."""
+        if store is None:
+            return IStore(cls)
+        else:
+            return store
+
+    @classmethod
+    def create(cls, build_farm_job, branch):
+        """See `ITranslationTemplatesBuildSource`."""
+        build = TranslationTemplatesBuild(build_farm_job, branch)
+        store = cls._getStore()
+        store.add(build)
+        store.flush()
+        return build
+
+    @classmethod
+    def get(cls, build_id, store=None):
+        """See `ITranslationTemplatesBuildSource`."""
+        store = cls._getStore(store)
+        match = store.find(
+            TranslationTemplatesBuild,
+            TranslationTemplatesBuild.id == build_id)
+        return match.one()
+
+    @classmethod
+    def getByBuildFarmJob(cls, buildfarmjob_id, store=None):
+        """See `ITranslationTemplatesBuildSource`."""
+        store = cls._getStore(store)
+        match = store.find(
+            TranslationTemplatesBuild,
+            TranslationTemplatesBuild.build_farm_job == buildfarmjob_id)
+        return match.one()
+
+    @classmethod
+    def findByBranch(cls, branch, store=None):
+        """See `ITranslationTemplatesBuildSource`."""
+        store = cls._getStore(store)
+        return store.find(
+            TranslationTemplatesBuild,
+            TranslationTemplatesBuild.branch == branch)
+
+
+def get_translation_templates_build_for_build_farm_job(build_farm_job):
+    """Return a `TranslationTemplatesBuild` from its `BuildFarmJob`."""
+    build = Store.of(build_farm_job).find(
+        TranslationTemplatesBuild,
+        TranslationTemplatesBuild.build_farm_job == build_farm_job).one()
+    return ProxyFactory(build)

=== modified file 'lib/lp/translations/model/translationtemplatesbuildbehavior.py'
--- lib/lp/translations/model/translationtemplatesbuildbehavior.py	2010-08-20 20:31:18 +0000
+++ lib/lp/translations/model/translationtemplatesbuildbehavior.py	2010-09-29 09:44:52 +0000
@@ -44,8 +44,10 @@
         self._builder.slave.cacheFile(logger, chroot)
         cookie = self.buildfarmjob.generateSlaveBuildCookie()
 
-        args = {'arch_tag': self._getDistroArchSeries().architecturetag}
-        args.update(self.buildfarmjob.metadata)
+        args = {
+            'arch_tag': self._getDistroArchSeries().architecturetag,
+            'branch_url': self.buildfarmjob.branch.composePublicURL(),
+            }
 
         filemap = {}
 

=== modified file 'lib/lp/translations/model/translationtemplatesbuildjob.py'
--- lib/lp/translations/model/translationtemplatesbuildjob.py	2010-08-27 15:03:18 +0000
+++ lib/lp/translations/model/translationtemplatesbuildjob.py	2010-09-29 09:44:52 +0000
@@ -3,6 +3,7 @@
 
 __metaclass__ = type
 __all__ = [
+    'HARDCODED_TRANSLATIONTEMPLATESBUILD_SCORE',
     'TranslationTemplatesBuildJob',
     ]
 
@@ -25,6 +26,7 @@
     )
 from lp.buildmaster.enums import BuildFarmJobType
 from lp.buildmaster.interfaces.buildfarmbranchjob import IBuildFarmBranchJob
+from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
 from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
 from lp.buildmaster.model.buildfarmjob import (
     BuildFarmJobOld,
@@ -37,12 +39,18 @@
     BranchJobDerived,
     BranchJobType,
     )
+from lp.translations.interfaces.translationtemplatesbuild import (
+    ITranslationTemplatesBuildSource,
+    )
 from lp.translations.interfaces.translationtemplatesbuildjob import (
     ITranslationTemplatesBuildJobSource,
     )
 from lp.translations.pottery.detect_intltool import is_intltool_structure
 
 
+HARDCODED_TRANSLATIONTEMPLATESBUILD_SCORE = 2510
+
+
 class TranslationTemplatesBuildJob(BuildFarmJobOldDerived, BranchJobDerived):
     """An `IBuildFarmJob` implementation that generates templates.
 
@@ -72,7 +80,7 @@
         # Hard-code score for now.  Most PPA jobs start out at 2505;
         # TranslationTemplateBuildJobs are fast so we want them at a
         # higher priority.
-        return 2510
+        return HARDCODED_TRANSLATIONTEMPLATESBUILD_SCORE
 
     def getLogFileName(self):
         """See `IBuildFarmJob`."""
@@ -128,25 +136,24 @@
     @classmethod
     def create(cls, branch):
         """See `ITranslationTemplatesBuildJobSource`."""
-        store = IMasterStore(BranchJob)
-
-        # Pass public HTTP URL for the branch.
-        metadata = {'branch_url': branch.composePublicURL()}
-        branch_job = BranchJob(
-            branch, BranchJobType.TRANSLATION_TEMPLATES_BUILD, metadata)
-        store.add(branch_job)
-        specific_job = TranslationTemplatesBuildJob(branch_job)
-        duration_estimate = cls.duration_estimate
-
         # XXX Danilo Segan bug=580429: we hard-code processor to the Ubuntu
         # default processor architecture.  This stops the buildfarm from
         # accidentally dispatching the jobs to private builders.
+        processor = cls._getBuildArch()
+
+        build_farm_job = getUtility(IBuildFarmJobSource).new(
+            BuildFarmJobType.TRANSLATIONTEMPLATESBUILD, processor=processor)
+        build = getUtility(ITranslationTemplatesBuildSource).create(
+            build_farm_job, branch)
+
+        specific_job = build.makeJob()
+        duration_estimate = cls.duration_estimate
+
         build_queue_entry = BuildQueue(
             estimated_duration=duration_estimate,
             job_type=BuildFarmJobType.TRANSLATIONTEMPLATESBUILD,
-            job=specific_job.job.id,
-            processor=cls._getBuildArch())
-        store.add(build_queue_entry)
+            job=specific_job.job, processor=processor)
+        IMasterStore(BuildQueue).add(build_queue_entry)
 
         return specific_job
 

=== modified file 'lib/lp/translations/stories/buildfarm/xx-build-summary.txt'
--- lib/lp/translations/stories/buildfarm/xx-build-summary.txt	2010-08-31 21:30:43 +0000
+++ lib/lp/translations/stories/buildfarm/xx-build-summary.txt	2010-09-29 09:44:52 +0000
@@ -1,13 +1,13 @@
-TranslationTemplatesBuildJob Build Summary
-==========================================
+TranslationTemplatesBuild Build Summary
+=======================================
 
-The builders UI can show TranslationTemplateBuildJobs, although they
+The builders UI can show TranslationTemplateBuild records, although they
 look a little different from Soyuz-style jobs.
 
 Setup
 -----
 
-Create a builder working on a TranslationTemplatesBuildJob for a branch.
+Create a builder working on a TranslationTemplatesBuild for a branch.
 
     >>> from zope.component import getUtility
     >>> from canonical.launchpad.interfaces.launchpad import (
@@ -76,3 +76,88 @@
     Working on TranslationTemplatesBuildJob for branch ...
 
     >>> user_browser.getLink(branch_url).click()
+
+
+Show build record
+-----------------
+
+There's a detailed page for the build record.
+
+    >>> login(ANONYMOUS)
+    >>> from lp.translations.interfaces.translationtemplatesbuild import (
+    ...     ITranslationTemplatesBuildSource,
+    ...     )
+    >>> build = getUtility(ITranslationTemplatesBuildSource).findByBranch(
+    ...     branch).one()
+    >>> build_url = canonical_url(build)
+    >>> logout()
+
+    >>> user_browser.open(build_url)
+    >>> print extract_text(find_main_content(user_browser.contents))
+    P...
+    Build status
+        Needs building
+        Not started yet.
+    Build details
+        Branch: lp://dev/...
+        Not imported anywhere.
+
+The "Not imported anywhere" is because nobody actually imports templates
+from this branch.  In practice, this will usually show exactly one
+product series.
+
+    >>> from lp.testing.sampledata import ADMIN_EMAIL
+    >>> login(ADMIN_EMAIL)
+    >>> from lp.translations.interfaces.translations import (
+    ...     TranslationsBranchImportMode,
+    ...     )
+    >>> product = factory.makeProduct(name='qblark', displayname='qblark')
+    >>> trunk = product.getSeries('trunk')
+    >>> trunk.branch = branch
+    >>> trunk.translations_autoimport_mode = (
+    ...     TranslationsBranchImportMode.IMPORT_TEMPLATES)
+    >>> logout()
+
+    >>> user_browser.open(build_url)
+    >>> print extract_text(find_main_content(user_browser.contents))
+    P...
+    Build status
+        Needs building
+        Not started yet.
+    Build details
+        Branch: lp://dev/...
+        For import into: qblark trunk series
+
+The listing links to the productseries that consumes the templates.
+
+    >>> user_browser.getLink("qblark trunk series").click()
+
+
+Build history
+-------------
+
+A completed translation templates build job shows up in the builder's
+build history.
+
+    >>> login(ANONYMOUS)
+    >>> from datetime import datetime, timedelta
+    >>> from pytz import utc
+    >>> from zope.security.proxy import removeSecurityProxy
+    >>> from lp.buildmaster.enums import BuildStatus
+    >>> naked_job = removeSecurityProxy(build.build_farm_job)
+    >>> naked_job.builder = builder
+    >>> naked_job.date_started = datetime(2010, 9, 14, 8, 0, tzinfo=utc)
+    >>> naked_job.date_finished = naked_job.date_started + timedelta(
+    ...     seconds=300)
+    >>> naked_job.status = BuildStatus.FULLYBUILT
+    >>> logout()
+
+    >>> user_browser.open(builder_page)
+    >>> user_browser.getLink('View full history').click()
+
+    >>> print extract_text(find_main_content(user_browser.contents))
+    Build history for ...
+    1 ... 1 of 1 result
+    ...
+    Translation template build
+    Build started ... and finished ... taking five minutes

=== added file 'lib/lp/translations/templates/translationtemplatesbuild-index.pt'
--- lib/lp/translations/templates/translationtemplatesbuild-index.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/templates/translationtemplatesbuild-index.pt	2010-09-29 09:44:52 +0000
@@ -0,0 +1,133 @@
+<html
+  xmlns="http://www.w3.org/1999/xhtml";
+  xmlns:tal="http://xml.zope.org/namespaces/tal";
+  xmlns:metal="http://xml.zope.org/namespaces/metal";
+  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+  metal:use-macro="view/macro:page/main_only"
+  i18n:domain="launchpad"
+>
+
+  <body>
+
+    <tal:registering metal:fill-slot="registering">
+        created
+        <span tal:content="context/date_created/fmt:displaydate"
+              tal:attributes="title context/date_created/fmt:datetime"
+          >on 2005-01-01</span>
+    </tal:registering>
+
+    <div metal:fill-slot="main">
+
+      <div class="yui-g">
+
+        <div id="status" class="yui-u first">
+          <div class="portlet">
+            <div metal:use-macro="template/macros/status" />
+          </div>
+        </div>
+
+        <div id="details" class="yui-u">
+          <div class="portlet">
+            <div metal:use-macro="template/macros/details" />
+          </div>
+        </div>
+
+      </div> <!-- yui-g  -->
+
+      <div id="buildlog" class="portlet"
+           tal:condition="context/status/enumvalue:BUILDING">
+        <div metal:use-macro="template/macros/buildlog" />
+      </div>
+
+   </div> <!-- main -->
+
+
+<metal:macros fill-slot="bogus">
+
+  <metal:macro define-macro="details">
+    <tal:comment replace="nothing">
+      Details section.
+    </tal:comment>
+    <h2>Build details</h2>
+    <p>Branch:
+     <tal:branch replace="structure context/branch/fmt:link">
+       lp:foo/trunk
+     </tal:branch>
+    </p>
+    <tal:targets tal:define="targets view/getTargets">
+      <div tal:condition="targets">
+        For import into:
+        <ul>
+          <li tal:repeat="target targets">
+            <a tal:replace="structure target/fmt:link">gawk trunk series</a>
+          </li>
+        </ul>
+      </div>
+      <div tal:condition="not:targets">
+        <em>Not imported anywhere.</em>
+      </div>
+    </tal:targets>
+  </metal:macro>
+
+  <metal:macro define-macro="status">
+    <tal:comment replace="nothing">
+      Status section.
+    </tal:comment>
+    <h2>Build status</h2>
+    <p>
+      <span tal:replace="structure context/image:icon" />
+      <span tal:attributes="
+            class string:buildstatus${context/status/name};"
+            tal:content="context/status/title">Fully built</span>
+      <tal:building define="builder context/builder"
+                    condition="builder">
+        on <a tal:content="builder/title"
+              tal:attributes="href builder/fmt:url"/>
+      </tal:building>
+    </p>
+
+    <ul>
+      <li tal:define="time view/renderDispatchTime"
+          tal:condition="time"
+          tal:content="structure time">
+          Started 5 minutes ago
+      </li>
+      <li tal:define="time view/renderFinishTime"
+          tal:condition="time"
+          tal:content="structure time">
+        Finished 30 seconds ago
+        <tal:duration define="duration context/duration" condition="duration">
+          (took <span tal:replace="duration/fmt:exactduration" />)
+        </tal:duration>
+      </li>
+
+      <li tal:define="file context/log"
+          tal:condition="file">
+        <a class="sprite download"
+           tal:attributes="href context/log_url"
+           tal:content="string: buildlog">BUILDLOG</a>
+        (<span tal:replace="file/content/filesize/fmt:bytes" />)
+      </li>
+    </ul>
+  </metal:macro>
+
+  <metal:macro define-macro="buildlog">
+    <tal:comment replace="nothing">
+      Buildlog section.
+    </tal:comment>
+    <h2>Buildlog</h2>
+    <div id="buildlog-tail" class="logtail"
+         tal:define="logtail context/buildqueue_record/logtail"
+         tal:content="structure logtail/fmt:text-to-html">
+      <p>Things are crashing and burning all over the place.</p>
+    </div>
+    <p class="discreet" tal:condition="view/user">
+      Updated on <span tal:replace="structure view/user/fmt:local-time"/>
+    </p>
+  </metal:macro>
+
+</metal:macros>
+
+
+  </body>
+</html>

=== added file 'lib/lp/translations/tests/test_translationtemplatesbuild.py'
--- lib/lp/translations/tests/test_translationtemplatesbuild.py	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/tests/test_translationtemplatesbuild.py	2010-09-29 09:44:52 +0000
@@ -0,0 +1,134 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""`TranslationTemplatesBuild` tests."""
+
+__metaclass__ = type
+
+from storm.store import Store
+import transaction
+from zope.component import getUtility
+from zope.interface.verify import verifyObject
+
+from canonical.config import config
+from canonical.testing import LaunchpadZopelessLayer
+from lp.buildmaster.enums import BuildFarmJobType
+from lp.buildmaster.interfaces.buildfarmjob import (
+    IBuildFarmJob,
+    IBuildFarmJobSource,
+    )
+from lp.testing import TestCaseWithFactory
+from lp.translations.interfaces.translationtemplatesbuild import (
+    ITranslationTemplatesBuild,
+    ITranslationTemplatesBuildSource,
+    )
+from lp.translations.model.translationtemplatesbuild import (
+    TranslationTemplatesBuild,
+    )
+from lp.translations.interfaces.translationtemplatesbuildjob import (
+    ITranslationTemplatesBuildJobSource,
+    )
+
+
+class TestTranslationTemplatesBuild(TestCaseWithFactory):
+
+    layer = LaunchpadZopelessLayer
+
+    def _makeBuildFarmJob(self):
+        """Create a `BuildFarmJob` for testing."""
+        source = getUtility(IBuildFarmJobSource)
+        return source.new(BuildFarmJobType.TRANSLATIONTEMPLATESBUILD)
+
+    def test_baseline(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        branch = self.factory.makeBranch()
+        build_farm_job = self._makeBuildFarmJob()
+
+        build = source.create(build_farm_job, branch)
+
+        self.assertTrue(verifyObject(ITranslationTemplatesBuild, build))
+        self.assertTrue(verifyObject(IBuildFarmJob, build))
+        self.assertEqual(build_farm_job, build.build_farm_job)
+        self.assertEqual(branch, build.branch)
+
+    def test_permissions(self):
+        # The branch scanner creates TranslationTemplatesBuilds.  It has
+        # the database privileges it needs for that.
+        branch = self.factory.makeBranch()
+        transaction.commit()
+        self.layer.switchDbUser(config.branchscanner.dbuser)
+        utility = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        specific_job = utility.create(build_farm_job, branch)
+
+        # Writing the new objects to the database violates no access
+        # restrictions.
+        Store.of(build_farm_job).flush()
+
+    def test_created_by_buildjobsource(self):
+        # ITranslationTemplatesBuildJobSource.create also creates a
+        # TranslationTemplatesBuild.  This utility will become obsolete
+        # later.
+        jobset = getUtility(ITranslationTemplatesBuildJobSource)
+        source = getUtility(ITranslationTemplatesBuildSource)
+        branch = self.factory.makeBranch()
+
+        translationtemplatesbuildjob = jobset.create(branch)
+
+        builds = list(source.findByBranch(branch))
+        self.assertEqual(1, len(builds))
+        self.assertIsInstance(builds[0], TranslationTemplatesBuild)
+
+    def test_getSpecificJob(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+        build = source.create(build_farm_job, branch)
+
+        self.assertEqual(build, build_farm_job.getSpecificJob())
+
+    def test_findByBranch(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+
+        self.assertContentEqual([], source.findByBranch(branch))
+
+        build = source.create(build_farm_job, branch)
+
+        by_branch = list(source.findByBranch(branch))
+        self.assertEqual([build], by_branch)
+
+    def test_get(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+        build = source.create(build_farm_job, branch)
+
+        self.assertEqual(build, source.get(build.id))
+
+    def test_get_returns_none_if_not_found(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+        build = source.create(build_farm_job, branch)
+
+        self.assertIs(None, source.get(build.id + 999))
+
+    def test_getByBuildFarmJob(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+        build = source.create(build_farm_job, branch)
+
+        self.assertEqual(build, source.getByBuildFarmJob(build_farm_job.id))
+
+    def test_getByBuildFarmJob_returns_none_if_not_found(self):
+        source = getUtility(ITranslationTemplatesBuildSource)
+        build_farm_job = self._makeBuildFarmJob()
+        branch = self.factory.makeBranch()
+        build = source.create(build_farm_job, branch)
+
+        self.assertIs(
+            None,
+            source.getByBuildFarmJob(build_farm_job.id + 999))


Follow ups