Merge lp:~edwin-grubbs/launchpad/all-downloads-link-sprite into lp:launchpad

Proposed by Edwin Grubbs
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~edwin-grubbs/launchpad/all-downloads-link-sprite
Merge into: lp:launchpad
Diff against target: 3199 lines
58 files modified
database/sampledata/current-dev.sql (+12/-12)
database/schema/Makefile (+3/-3)
database/schema/comments.sql (+20/-1)
database/schema/fti.py (+8/-12)
database/schema/patch-2207-00-3.sql (+6/-1)
database/schema/patch-2207-04-0.sql (+13/-0)
database/schema/patch-2207-05-0.sql (+12/-0)
database/schema/patch-2207-06-0.sql (+122/-0)
database/schema/patch-2207-08-0.sql (+20/-0)
database/schema/security.cfg (+9/-1)
database/schema/trusted.sql (+15/-0)
database/schema/unautovacuumable.py (+25/-7)
lib/canonical/config/schema-lazr.conf (+0/-7)
lib/canonical/ftests/pgsql.py (+11/-0)
lib/canonical/launchpad/emailtemplates/branch-merge-proposal-created.txt (+1/-1)
lib/canonical/launchpad/icing/style-3-0.css (+3/-0)
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+10/-0)
lib/canonical/launchpad/templates/launchpad-login.pt (+7/-7)
lib/lp/bugs/doc/externalbugtracker-debbugs.txt (+2/-2)
lib/lp/code/browser/branch.py (+12/-2)
lib/lp/code/mail/branchmergeproposal.py (+5/-0)
lib/lp/code/mail/tests/test_branchmergeproposal.py (+16/-3)
lib/lp/code/stories/branches/xx-branch-merge-proposals.txt (+17/-3)
lib/lp/code/templates/branchmergeproposal-pagelet-summary.pt (+4/-0)
lib/lp/code/templates/branchmergeproposal-resubmit.pt (+3/-2)
lib/lp/registry/browser/__init__.py (+47/-9)
lib/lp/registry/browser/productseries.py (+7/-2)
lib/lp/registry/browser/tests/milestone-views.txt (+6/-0)
lib/lp/registry/browser/tests/productseries-views.txt (+38/-9)
lib/lp/registry/doc/milestone.txt (+10/-0)
lib/lp/registry/model/distribution.py (+1/-0)
lib/lp/registry/model/milestone.py (+4/-0)
lib/lp/registry/templates/product-index.pt (+1/-1)
lib/lp/registry/templates/productseries-delete.pt (+12/-6)
lib/lp/soyuz/adapters/archivedependencies.py (+3/-6)
lib/lp/soyuz/browser/archive.py (+38/-2)
lib/lp/soyuz/browser/configure.zcml (+1/-1)
lib/lp/soyuz/browser/packageset.py (+19/-0)
lib/lp/soyuz/browser/tests/archive-views.txt (+47/-0)
lib/lp/soyuz/configure.zcml (+10/-2)
lib/lp/soyuz/doc/archive-dependencies.txt (+14/-28)
lib/lp/soyuz/doc/archive.txt (+23/-5)
lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt (+1/-0)
lib/lp/soyuz/doc/package-cache.txt (+4/-2)
lib/lp/soyuz/interfaces/archive.py (+36/-11)
lib/lp/soyuz/interfaces/archivepermission.py (+14/-1)
lib/lp/soyuz/interfaces/packageset.py (+56/-6)
lib/lp/soyuz/interfaces/packagesetgroup.py (+41/-0)
lib/lp/soyuz/model/archive.py (+9/-2)
lib/lp/soyuz/model/archivepermission.py (+21/-8)
lib/lp/soyuz/model/packageset.py (+76/-7)
lib/lp/soyuz/model/packagesetgroup.py (+30/-0)
lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt (+25/-2)
lib/lp/soyuz/stories/webservice/xx-packageset.txt (+234/-60)
lib/lp/soyuz/templates/person-archive-subscriptions.pt (+1/-1)
lib/lp/soyuz/tests/test_packageset.py (+187/-0)
scripts/ftpmaster-tools/_syncorigins.py (+1/-1)
utilities/pgmassacre.py (+19/-3)
To merge this branch: bzr merge lp:~edwin-grubbs/launchpad/all-downloads-link-sprite
Reviewer Review Type Date Requested Status
Michael Nelson (community) release-critical Approve
Curtis Hovey (community) code Approve
Review via email: mp+14333@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Summary
-------

In a previous branch, I removed the stylesheet for a.info, since it
was interfering with the "info" class for sprites. This broke a couple
of links. This was corrected by setting class="sprite info".

See the "All downloads" link on https://edge.launchpad.net/bzr

BTW, I also merged devel into db-devel, so that I won't have
to worry about conflicts when pqm is finally working.

Demo and Q/A
------------

* Open https://launchpad.dev/firefox

The other page where this occurs is difficult to view, since
you must be subscribed to a Private PPA. You can subscribe
the name16 (foo.bar) user on launchpad.dev with the following
sql statements.

UPDATE Archive SET private = TRUE, buildd_secret = 'foo' WHERE id = 13;

INSERT INTO ArchiveSubscriber (archive, registrant, subscriber, status)
VALUES (13, 1, 16, 1);

INSERT INTO ArchiveAuthToken (archive, person, token)
VALUES (13, 16, 'asdf');

* https://launchpad.dev/~name16/+archivesubscriptions

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Here is the real diff:

{{{
=== modified file 'lib/lp/registry/templates/product-index.pt'
--- lib/lp/registry/templates/product-index.pt 2009-10-08 15:54:09 +0000
+++ lib/lp/registry/templates/product-index.pt 2009-11-02 21:44:55 +0000
@@ -219,7 +219,7 @@
         </tal:release>

         <p class="alternate">
- <a class="sprint info"
+ <a class="sprite info"
             tal:define="link overview_menu/downloads"
             tal:condition="release"
             tal:attributes="href link/fmt:url;

=== modified file 'lib/lp/soyuz/templates/person-archive-subscriptions.pt'
--- lib/lp/soyuz/templates/person-archive-subscriptions.pt 2009-09-18 07:46:03 +0000
+++ lib/lp/soyuz/templates/person-archive-subscriptions.pt 2009-11-02 21:44:55 +0000
@@ -34,7 +34,7 @@
                 </td>
                 <td>
                   <tal:active condition="token">
- <a tal:attributes="href subscription/fmt:url" class="info">
+ <a tal:attributes="href subscription/fmt:url" class="sprite info">
                       View
                     </a>
                   </tal:active>
}}}

Revision history for this message
Curtis Hovey (sinzui) wrote :

This looks good to land. Thanks for fixing the typo and adding the missing class.

review: Approve (code)
Revision history for this message
Michael Nelson (michael.nelson) wrote :

Approved for the 'real' diff that you've listed. As you said, it'd be great to wait until the last devel changes hit db-devel so you can verify the real diff.

review: Approve (release-critical)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/sampledata/current-dev.sql'
2--- database/sampledata/current-dev.sql 2009-09-07 01:46:23 +0000
3+++ database/sampledata/current-dev.sql 2009-11-03 18:13:40 +0000
4@@ -969,18 +969,18 @@
5
6 ALTER TABLE archive DISABLE TRIGGER ALL;
7
8-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (1, 17, NULL, true, NULL, 1, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:12.241774', 15, 1, 8, 5, 1, '2008-09-23 17:29:03.442606', NULL, NULL, NULL, 'Primary Archive for Ubuntu Linux', 0);
9-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (2, 1, NULL, true, NULL, 2, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.863812', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.445921', NULL, NULL, NULL, 'Primary Archive for Redhat Advanced Server', 0);
10-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (3, 1, NULL, true, NULL, 3, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.864941', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.446557', NULL, NULL, NULL, 'Primary Archive for Debian GNU/Linux', 0);
11-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (4, 1, NULL, true, NULL, 4, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.865502', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.44689', NULL, NULL, NULL, 'Primary Archive for The Gentoo Linux', 0);
12-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (5, 1, NULL, true, NULL, 5, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.866015', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.447202', NULL, NULL, NULL, 'Primary Archive for Kubuntu - Free KDE-based Linux', 0);
13-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (7, 4, NULL, true, NULL, 7, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.866529', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.447515', NULL, NULL, NULL, 'Primary Archive for GuadaLinex: Linux for Andalucia', 0);
14-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (8, 17, NULL, true, NULL, 8, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.867154', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.447851', NULL, NULL, NULL, 'Primary Archive for Ubuntu Test', 0);
15-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (9, 28, 'packages to help my friends.', true, 1024, 1, 2, false, 3, 3, NULL, NULL, NULL, true, 'ppa', true, '2008-05-27 18:15:15.867684', 4, 0, 3, 1, 0, '2008-09-23 17:29:03.448178', NULL, NULL, NULL, 'PPA for Celso Providelo', 0);
16-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (10, 1, 'packages to help the humanity (you know, ubuntu)', true, 1024, 1, 2, false, 1, 1, NULL, NULL, NULL, true, 'ppa', true, '2008-05-27 18:15:15.868202', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.448488', NULL, NULL, NULL, 'PPA for Mark Shuttleworth', 0);
17-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (11, 52, 'I am not allowed to say, I have no privs.', true, 1024, 1, 2, false, 0, 0, NULL, NULL, NULL, true, 'ppa', true, '2008-05-27 18:15:15.868709', 1, 0, 0, 1, 0, '2008-09-23 17:29:03.448797', NULL, NULL, NULL, 'PPA for No Privileges Person', 0);
18-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (12, 17, 'Partner archive', true, NULL, 1, 4, false, NULL, NULL, NULL, NULL, NULL, false, 'partner', true, '2008-05-27 18:15:15.869209', 1, 0, 1, 0, 0, '2008-09-23 17:29:03.449157', NULL, NULL, NULL, 'Partner Archive for Ubuntu Linux', 0);
19-INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score) VALUES (13, 17, 'Partner archive', true, NULL, 8, 4, false, NULL, NULL, NULL, NULL, NULL, false, 'partner', true, '2008-05-27 18:15:15.869732', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.449471', NULL, NULL, NULL, 'Partner Archive for Ubuntu Test', 0);
20+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (1, 17, NULL, true, NULL, 1, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:12.241774', 15, 1, 8, 5, 1, '2008-09-23 17:29:03.442606', NULL, NULL, NULL, 'Primary Archive for Ubuntu Linux', 0, NULL);
21+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (2, 1, NULL, true, NULL, 2, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.863812', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.445921', NULL, NULL, NULL, 'Primary Archive for Redhat Advanced Server', 0, NULL);
22+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (3, 1, NULL, true, NULL, 3, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.864941', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.446557', NULL, NULL, NULL, 'Primary Archive for Debian GNU/Linux', 0, NULL);
23+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (4, 1, NULL, true, NULL, 4, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.865502', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.44689', NULL, NULL, NULL, 'Primary Archive for The Gentoo Linux', 0, NULL);
24+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (5, 1, NULL, true, NULL, 5, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.866015', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.447202', NULL, NULL, NULL, 'Primary Archive for Kubuntu - Free KDE-based Linux', 0, NULL);
25+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (7, 4, NULL, true, NULL, 7, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.866529', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.447515', NULL, NULL, NULL, 'Primary Archive for GuadaLinex: Linux for Andalucia', 0, NULL);
26+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (8, 17, NULL, true, NULL, 8, 1, false, NULL, NULL, NULL, NULL, NULL, false, 'primary', true, '2008-05-27 18:15:15.867154', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.447851', NULL, NULL, NULL, 'Primary Archive for Ubuntu Test', 0, NULL);
27+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (9, 28, 'packages to help my friends.', true, 1024, 1, 2, false, 3, 3, NULL, NULL, NULL, true, 'ppa', true, '2008-05-27 18:15:15.867684', 4, 0, 3, 1, 0, '2008-09-23 17:29:03.448178', NULL, NULL, NULL, 'PPA for Celso Providelo', 0, NULL);
28+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (10, 1, 'packages to help the humanity (you know, ubuntu)', true, 1024, 1, 2, false, 1, 1, NULL, NULL, NULL, true, 'ppa', true, '2008-05-27 18:15:15.868202', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.448488', NULL, NULL, NULL, 'PPA for Mark Shuttleworth', 0, NULL);
29+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (11, 52, 'I am not allowed to say, I have no privs.', true, 1024, 1, 2, false, 0, 0, NULL, NULL, NULL, true, 'ppa', true, '2008-05-27 18:15:15.868709', 1, 0, 0, 1, 0, '2008-09-23 17:29:03.448797', NULL, NULL, NULL, 'PPA for No Privileges Person', 0, NULL);
30+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (12, 17, 'Partner archive', true, NULL, 1, 4, false, NULL, NULL, NULL, NULL, NULL, false, 'partner', true, '2008-05-27 18:15:15.869209', 1, 0, 1, 0, 0, '2008-09-23 17:29:03.449157', NULL, NULL, NULL, 'Partner Archive for Ubuntu Linux', 0, NULL);
31+INSERT INTO archive (id, owner, description, enabled, authorized_size, distribution, purpose, private, sources_cached, binaries_cached, package_description_cache, fti, buildd_secret, require_virtualized, name, publish, date_updated, total_count, pending_count, succeeded_count, failed_count, building_count, date_created, signing_key, removed_binary_retention_days, num_old_versions_published, displayname, relative_build_score, external_dependencies) VALUES (13, 17, 'Partner archive', true, NULL, 8, 4, false, NULL, NULL, NULL, NULL, NULL, false, 'partner', true, '2008-05-27 18:15:15.869732', 0, 0, 0, 0, 0, '2008-09-23 17:29:03.449471', NULL, NULL, NULL, 'Partner Archive for Ubuntu Test', 0, NULL);
32
33
34 ALTER TABLE archive ENABLE TRIGGER ALL;
35
36=== modified file 'database/schema/Makefile'
37--- database/schema/Makefile 2009-08-04 22:16:48 +0000
38+++ database/schema/Makefile 2009-11-03 18:13:41 +0000
39@@ -113,7 +113,7 @@
40 @ echo "* eg. sudo -u postgres make create"
41 @ echo
42 @ echo "* Creating database \"$(EMPTY_DBNAME)\"."
43- @ ${CREATEDB} template1 ${EMPTY_DBNAME}
44+ @ ${CREATEDB} template0 ${EMPTY_DBNAME}
45 @ if ! `createlang -l ${EMPTY_DBNAME} | grep -qs plpythonu`; then \
46 echo "* Installing PL/PythonU"; \
47 createlang -d ${EMPTY_DBNAME} plpythonu; \
48@@ -142,12 +142,12 @@
49
50 @ echo "* Creating session databases '${SESSION_DBNAME}' (if necessary)"
51 @if [ "$$((`psql -l | grep -w ${SESSION_DBNAME} | wc -l`))" = '0' ]; \
52- then ${CREATEDB} template1 ${SESSION_DBNAME} ; \
53+ then ${CREATEDB} template0 ${SESSION_DBNAME} ; \
54 createlang plpgsql ${SESSION_DBNAME}; \
55 psql -q -d ${SESSION_DBNAME} -f launchpad_session.sql ; \
56 fi
57 @ echo "* Creating session database '${TEST_SESSION_DBNAME}'"
58- @ ${CREATEDB} template1 ${TEST_SESSION_DBNAME}
59+ @ ${CREATEDB} template0 ${TEST_SESSION_DBNAME}
60 @ createlang plpgsql ${TEST_SESSION_DBNAME}
61 @ psql -q -d ${TEST_SESSION_DBNAME} -f launchpad_session.sql
62
63
64=== modified file 'database/schema/comments.sql'
65--- database/schema/comments.sql 2009-08-30 01:41:38 +0000
66+++ database/schema/comments.sql 2009-11-03 18:13:41 +0000
67@@ -763,8 +763,11 @@
68
69 -- SprintAttendance
70 COMMENT ON TABLE SprintAttendance IS 'The record that someone will be attending a particular sprint or meeting.';
71+COMMENT ON COLUMN SprintAttendance.attendee IS 'The person attending the sprint.';
72+COMMENT ON COLUMN SprintAttendance.sprint IS 'The sprint the person is attending.';
73 COMMENT ON COLUMN SprintAttendance.time_starts IS 'The time from which the person will be available to participate in meetings at the sprint.';
74 COMMENT ON COLUMN SprintAttendance.time_ends IS 'The time of departure from the sprint or conference - this is the last time at which the person is available for meetings during the sprint.';
75+COMMENT ON COLUMN SprintAttendance.is_physical IS 'Is the person physically attending the sprint';
76
77
78 -- SprintSpecification
79@@ -1862,6 +1865,7 @@
80 COMMENT ON COLUMN Archive.removed_binary_retention_days IS 'The number of days before superseded or deleted binary files are expired in the librarian, or zero for never.';
81 COMMENT ON COLUMN Archive.num_old_versions_published IS 'The number of versions of a package to keep published before older versions are superseded.';
82 COMMENT ON COLUMN Archive.relative_build_score IS 'A delta to the build score that is applied to all builds in this archive.';
83+COMMENT ON COLUMN Archive.external_dependencies IS 'Newline-separated list of repositories to be used to retrieve any external build dependencies when building packages in this archive, in the format: deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] [components] The series variable is replaced with the series name of the context build. This column is specifically and only intended for OEM migration to Launchpad and should be re-examined in October 2010 to see if it is still relevant.';
84
85 -- ArchiveAuthToken
86
87@@ -2235,11 +2239,20 @@
88
89 -- Packageset
90
91-COMMENT ON TABLE Packageset IS 'Package sets facilitate the grouping of packages for purposes like the control of upload permissions, et.';
92+COMMENT ON TABLE Packageset IS 'Package sets facilitate the grouping of packages (in a given distro series) for purposes like the control of upload permissions, etc.';
93 COMMENT ON COLUMN Packageset.date_created IS 'Date and time of creation.';
94 COMMENT ON COLUMN Packageset.owner IS 'The Person or team who owns the package set';
95 COMMENT ON COLUMN Packageset.name IS 'The name for the package set on hand.';
96 COMMENT ON COLUMN Packageset.description IS 'The description for the package set on hand.';
97+COMMENT ON COLUMN Packageset.packagesetgroup IS 'The group this package set is affiliated with.';
98+COMMENT ON COLUMN Packageset.distroseries IS 'The distro series this package set belongs to.';
99+
100+-- PackagesetGroup
101+
102+COMMENT ON TABLE PackagesetGroup IS 'Package set groups keep track of equivalent package sets across distro series boundaries.';
103+COMMENT ON COLUMN Packageset.date_created IS 'Date and time of creation.';
104+COMMENT ON COLUMN Packageset.owner IS 'The Person or team who owns the package
105+set group.';
106
107 -- PackagesetSources
108
109@@ -2256,3 +2269,9 @@
110 COMMENT ON TABLE FlatPackagesetInclusion IS 'In order to facilitate the querying of set-subset relationships an expanded or flattened representation of the set-subset hierarchy is provided by this table.';
111 COMMENT ON COLUMN FlatPackagesetInclusion.parent IS 'The package set that is (directly or indirectly) including a subset.';
112 COMMENT ON COLUMN FlatPackagesetInclusion.child IS 'The package set that is being included as a subset.';
113+
114+-- SourcePackageFormatSelection
115+COMMENT ON TABLE SourcePackageFormatSelection IS 'Allowed source package formats for a given distroseries.';
116+COMMENT ON COLUMN SourcePackageFormatSelection.distroseries IS 'Refers to the distroseries in question.';
117+COMMENT ON COLUMN SourcePackageFormatSelection.format IS 'The SourcePackageFormat to allow.';
118+
119
120=== modified file 'database/schema/fti.py'
121--- database/schema/fti.py 2009-06-24 21:17:33 +0000
122+++ database/schema/fti.py 2009-11-03 18:13:42 +0000
123@@ -13,6 +13,7 @@
124
125 import _pythonpath
126
127+from distutils.version import LooseVersion
128 import sys
129 import os.path
130 from optparse import OptionParser
131@@ -287,9 +288,9 @@
132 """Setup and install tsearch2 if isn't already"""
133
134 # tsearch2 is out-of-the-box in 8.3+
135- v83 = get_pgversion(con).startswith('8.3')
136-
137- assert v83, 'This script only supports PostgreSQL 8.3'
138+ required = LooseVersion('8.3.0')
139+ assert get_pgversion(con) >= required, (
140+ 'This script only supports PostgreSQL 8.3+')
141
142 schema_exists = bool(execute(
143 con, "SELECT COUNT(*) FROM pg_namespace WHERE nspname='ts2'",
144@@ -608,18 +609,13 @@
145
146 def get_pgversion(con):
147 rows = execute(con, r"show server_version", results=True)
148- return rows[0][0]
149+ return LooseVersion(rows[0][0])
150
151
152 def get_tsearch2_sql_path(con):
153- pgversion = get_pgversion(con)
154- if pgversion.startswith('8.2.'):
155- path = os.path.join(PGSQL_BASE, '8.2', 'contrib', 'tsearch2.sql')
156- elif pgversion.startswith('8.3.'):
157- path = os.path.join(PGSQL_BASE, '8.3', 'contrib', 'tsearch2.sql')
158- else:
159- raise RuntimeError('Unknown version %s' % pgversion)
160-
161+ major, minor = get_pgversion(con).version[:2]
162+ path = os.path.join(
163+ PGSQL_BASE, '%d.%d' % (major, minor), 'contrib', 'tsearch2.sql')
164 assert os.path.exists(path), '%s does not exist' % path
165 return path
166
167
168=== modified file 'database/schema/patch-2207-00-3.sql'
169--- database/schema/patch-2207-00-3.sql 2009-09-18 04:13:18 +0000
170+++ database/schema/patch-2207-00-3.sql 2009-11-03 18:13:41 +0000
171@@ -1,7 +1,12 @@
172 SET client_min_messages TO ERROR;
173
174 UPDATE BugActivity SET person=(SELECT id FROM Person WHERE name='janitor')
175-WHERE person NOT IN (SELECT id FROM Person);
176+FROM (
177+ SELECT BugActivity.id
178+ FROM BugActivity LEFT OUTER JOIN Person ON BugActivity.person = Person.id
179+ WHERE Person.id IS NULL
180+ ) AS Whatever
181+WHERE Whatever.id = BugActivity.id;
182
183 ALTER TABLE BugActivity
184 ADD CONSTRAINT bugactivity__person__fk
185
186=== added file 'database/schema/patch-2207-04-0.sql'
187--- database/schema/patch-2207-04-0.sql 1970-01-01 00:00:00 +0000
188+++ database/schema/patch-2207-04-0.sql 2009-11-03 18:13:39 +0000
189@@ -0,0 +1,13 @@
190+-- Copyright 2009 Canonical Ltd. This software is licensed under the
191+-- GNU Affero General Public License version 3 (see the file LICENSE).
192+
193+SET client_min_messages=ERROR;
194+
195+
196+-- Add a column to indicate that the attendee is physically attending
197+-- the sprint.
198+ALTER TABLE SprintAttendance
199+ ADD COLUMN is_physical BOOLEAN NOT NULL DEFAULT FALSE;
200+
201+
202+INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 04, 0);
203
204=== added file 'database/schema/patch-2207-05-0.sql'
205--- database/schema/patch-2207-05-0.sql 1970-01-01 00:00:00 +0000
206+++ database/schema/patch-2207-05-0.sql 2009-11-03 18:13:40 +0000
207@@ -0,0 +1,12 @@
208+-- Copyright 2009 Canonical Ltd. This software is licensed under the
209+-- GNU Affero General Public License version 3 (see the file LICENSE).
210+
211+SET client_min_messages=ERROR;
212+
213+
214+-- Add a column for external archive dependencies.
215+ALTER TABLE Archive
216+ ADD COLUMN external_dependencies text DEFAULT NULL;
217+
218+
219+INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 05, 0);
220
221=== added file 'database/schema/patch-2207-06-0.sql'
222--- database/schema/patch-2207-06-0.sql 1970-01-01 00:00:00 +0000
223+++ database/schema/patch-2207-06-0.sql 2009-11-03 18:13:43 +0000
224@@ -0,0 +1,122 @@
225+-- Copyright 2009 Canonical Ltd. This software is licensed under the
226+-- GNU Affero General Public License version 3 (see the file LICENSE).
227+
228+SET client_min_messages=ERROR;
229+
230+-- ** PART 1 ** Create the 'packagesetgroup' table and the
231+-- 'packageset.packagesetgroup' foreign key,
232+-- populate the 'packagesetgroup' table
233+
234+-- This table keeps track of package sets that are equivalent across
235+-- distro series boundaries.
236+CREATE SEQUENCE packagesetgroup_id_seq
237+ START WITH 1
238+ INCREMENT BY 1
239+ NO MAXVALUE
240+ NO MINVALUE
241+ CACHE 1;
242+CREATE TABLE packagesetgroup (
243+ id integer NOT NULL DEFAULT nextval('packagesetgroup_id_seq'),
244+ date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
245+ owner integer NOT NULL,
246+ -- Please note: the 'name' column is only here to ease the data migration
247+ -- and will be dropped at the end of this patch.
248+ name text NOT NULL
249+);
250+ALTER SEQUENCE packagesetgroup_id_seq OWNED BY packagesetgroup.id;
251+ALTER TABLE ONLY packagesetgroup
252+ ADD CONSTRAINT packagesetgroup_pkey PRIMARY KEY (id);
253+ALTER TABLE ONLY packagesetgroup
254+ ADD CONSTRAINT packagesetgroup__owner__fk
255+ FOREIGN KEY (owner) REFERENCES person(id);
256+
257+-- Package sets and their clones belong to the same package set group.
258+ALTER TABLE ONLY packageset ADD COLUMN packagesetgroup integer;
259+ALTER TABLE ONLY packageset
260+ ADD CONSTRAINT packageset__packagesetgroup__fk
261+ FOREIGN KEY (packagesetgroup) REFERENCES packagesetgroup(id);
262+
263+-- Create a group for each of the original (karmic koala) package sets.
264+INSERT INTO packagesetgroup(owner, name)
265+SELECT packageset.owner, packageset.name
266+FROM packageset WHERE NOT packageset.name LIKE('lucid-%');
267+
268+
269+-- ** PART 2 ** Associate the karmic koala package sets and their lucid lynx
270+-- clones with the appropriate package set groups
271+
272+-- Update the karmic koala package sets so they reference their groups.
273+UPDATE packageset SET packagesetgroup = packagesetgroup.id
274+FROM packagesetgroup WHERE packageset.name = packagesetgroup.name;
275+
276+-- Update the lucid lynx package set *clones* so they reference their groups
277+-- as well.
278+UPDATE packageset SET packagesetgroup = packagesetgroup.id
279+FROM packagesetgroup WHERE packageset.name = 'lucid-' || packagesetgroup.name;
280+
281+-- ** PART 3 ** Add the 'packageset.distroseries' foreign key and
282+-- initialise it for the existing package sets.
283+
284+-- A package set lives in a distro series context.
285+ALTER TABLE ONLY packageset ADD COLUMN distroseries integer;
286+
287+-- Define the foreign key constraint.
288+ALTER TABLE ONLY packageset
289+ ADD CONSTRAINT packageset__distroseries__fk
290+ FOREIGN KEY (distroseries) REFERENCES distroseries(id);
291+
292+-- First migrate the original package sets created for the karmic koala.
293+UPDATE packageset SET distroseries = distroseries.id FROM distroseries
294+WHERE distroseries.name = 'karmic' AND NOT packageset.name LIKE('lucid-%');
295+
296+-- Migrate the lucid lynx package sets next.
297+UPDATE packageset SET distroseries = distroseries.id FROM distroseries
298+WHERE distroseries.name = 'lucid' AND packageset.name LIKE('lucid-%');
299+
300+-- Make the 'distroseries' foreign key mandatory.
301+ALTER TABLE ONLY packageset ALTER COLUMN distroseries SET NOT NULL;
302+
303+-- The package set name is now only unique in conjunction with a distro series.
304+ALTER TABLE ONLY packageset
305+ DROP CONSTRAINT packageset_name_key;
306+ALTER TABLE ONLY packageset
307+ ADD CONSTRAINT packageset__name__distroseries__key UNIQUE (name, distroseries);
308+
309+-- ** PART 4 ** Strip off the 'lucid-' prefix of the lucid lynx
310+-- package set names
311+UPDATE packageset SET name = substring(name FROM length('lucid-')+1)
312+WHERE name LIKE('lucid-%');
313+
314+-- ** PART 5 ** Create package set groups for package sets that were added in
315+-- lucid lynx but do not exist in the karmic koala,
316+-- associate these package sets with their newly created groups
317+INSERT INTO packagesetgroup(owner, name)
318+SELECT packageset.owner, packageset.name
319+FROM packageset, distroseries WHERE
320+ packageset.packagesetgroup IS NULL
321+ AND packageset.distroseries = distroseries.id
322+ AND distroseries.name = 'lucid';
323+
324+UPDATE packageset SET packagesetgroup = packagesetgroup.id
325+FROM packagesetgroup, distroseries
326+WHERE
327+ packageset.packagesetgroup IS NULL
328+ AND packageset.distroseries = distroseries.id
329+ AND distroseries.name = 'lucid'
330+ AND packageset.name = packagesetgroup.name;
331+
332+-- ** PART 6 ** Make the 'packageset.packagesetgroup' foreign key mandatory
333+ALTER TABLE ONLY packageset ALTER COLUMN packagesetgroup SET NOT NULL;
334+
335+-- ** PART 7 ** Drop the 'packagesetgroup.name' column that was only added
336+-- for data migration purposes.
337+ALTER TABLE ONLY packagesetgroup DROP COLUMN name;
338+
339+-- Define indices on the newly added foreign keys.
340+CREATE INDEX packageset__packagesetgroup__idx
341+ ON packageset(packagesetgroup);
342+CREATE INDEX packageset__distroseries__idx
343+ ON packageset(distroseries);
344+CREATE INDEX packagesetgroup__owner__idx ON PackageSetGroup(owner);
345+
346+INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 06, 0);
347
348=== added file 'database/schema/patch-2207-08-0.sql'
349--- database/schema/patch-2207-08-0.sql 1970-01-01 00:00:00 +0000
350+++ database/schema/patch-2207-08-0.sql 2009-11-03 18:13:40 +0000
351@@ -0,0 +1,20 @@
352+-- Copyright 2009 Canonical Ltd. This software is licensed under the
353+-- GNU Affero General Public License version 3 (see the file LICENSE).
354+
355+SET client_min_messages=ERROR;
356+
357+CREATE TABLE sourcepackageformatselection (
358+ id serial PRIMARY KEY,
359+ distroseries integer NOT NULL
360+ CONSTRAINT sourceformatselection__distroseries__fk
361+ REFERENCES distroseries,
362+ format integer NOT NULL,
363+ CONSTRAINT sourceformatselection__distroseries__format__key
364+ UNIQUE (distroseries, format)
365+);
366+
367+-- Allow all series to accept format 1.0 by default.
368+INSERT INTO sourcepackageformatselection (distroseries, format)
369+ SELECT id, 0 AS format FROM distroseries;
370+
371+INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 08, 0);
372
373=== modified file 'database/schema/security.cfg'
374--- database/schema/security.cfg 2009-10-31 01:41:29 +0000
375+++ database/schema/security.cfg 2009-11-03 18:13:41 +0000
376@@ -227,6 +227,7 @@
377 public.packagediff = SELECT, INSERT, UPDATE, DELETE
378 public.packagediff = SELECT, INSERT, UPDATE, DELETE
379 public.packageset = SELECT, INSERT, UPDATE, DELETE
380+public.packagesetgroup = SELECT, INSERT, UPDATE, DELETE
381 public.packagesetsources = SELECT, INSERT, UPDATE, DELETE
382 public.packagesetinclusion = SELECT, INSERT, UPDATE, DELETE
383 public.flatpackagesetinclusion = SELECT, INSERT, UPDATE, DELETE
384@@ -649,6 +650,7 @@
385 public.messagechunk = SELECT, INSERT
386 # Merge notifications
387 public.codereviewvote = SELECT
388+public.codereviewmessage = SELECT
389
390 [branch-distro]
391 type=user
392@@ -785,6 +787,7 @@
393 public.packagecopyrequest = SELECT, INSERT, UPDATE
394 public.packagediff = SELECT, INSERT, UPDATE
395 public.packageset = SELECT
396+public.packagesetgroup = SELECT
397 public.packagesetsources = SELECT, INSERT, UPDATE, DELETE
398 public.packagesetinclusion = SELECT, INSERT, UPDATE, DELETE
399 public.flatpackagesetinclusion = SELECT, INSERT, UPDATE, DELETE
400@@ -862,6 +865,7 @@
401 public.teammembership = SELECT
402 public.gpgkey = SELECT
403 public.packageset = SELECT
404+public.packagesetgroup = SELECT
405 public.packagesetsources = SELECT
406 public.packagesetinclusion = SELECT
407 public.flatpackagesetinclusion = SELECT
408@@ -917,7 +921,7 @@
409 public.bugpackageinfestation = SELECT, INSERT, UPDATE
410 public.bugproductinfestation = SELECT, INSERT, UPDATE
411 public.bugsubscription = SELECT, INSERT, UPDATE, DELETE
412-public.bugtask = SELECT, INSERT, UPDATE
413+public.bugtask = SELECT, INSERT, UPDATE, DELETE
414 public.bugtracker = SELECT, INSERT, UPDATE, DELETE
415 public.bugtrackeralias = SELECT, INSERT, UPDATE, DELETE
416 public.bugwatch = SELECT, INSERT, UPDATE, DELETE
417@@ -1062,6 +1066,7 @@
418 public.archive = SELECT, INSERT, UPDATE
419 public.archivearch = SELECT, INSERT, UPDATE
420 public.packageset = SELECT
421+public.packagesetgroup = SELECT
422 public.packagesetsources = SELECT
423 public.packagesetinclusion = SELECT
424 public.flatpackagesetinclusion = SELECT
425@@ -1250,6 +1255,7 @@
426 public.personlanguage = SELECT
427 public.structuralsubscription = SELECT
428 public.packageset = SELECT
429+public.packagesetgroup = SELECT
430 public.packagesetsources = SELECT
431 public.packagesetinclusion = SELECT
432 public.flatpackagesetinclusion = SELECT
433@@ -1702,6 +1708,7 @@
434 public.productseries = SELECT
435 public.revision = SELECT
436 public.revisionauthor = SELECT, INSERT
437+public.seriessourcepackagebranch = SELECT
438 public.sourcepackagename = SELECT
439 public.staticdiff = SELECT, INSERT
440 public.teammembership = SELECT
441@@ -1807,6 +1814,7 @@
442 type=user
443 public.libraryfilecontent = SELECT
444 public.openidrpconfig = SELECT
445+public.branch = SELECT
446
447 [modified-branches]
448 type=user
449
450=== modified file 'database/schema/trusted.sql'
451--- database/schema/trusted.sql 2009-08-19 15:35:13 +0000
452+++ database/schema/trusted.sql 2009-11-03 18:13:42 +0000
453@@ -1029,7 +1029,22 @@
454 DECLARE
455 parent_name text;
456 child_name text;
457+ parent_distroseries text;
458+ child_distroseries text;
459 BEGIN
460+ -- Make sure that the package sets being associated here belong
461+ -- to the same distro series.
462+ IF (SELECT parent.distroseries != child.distroseries
463+ FROM packageset parent, packageset child
464+ WHERE parent.id = NEW.parent AND child.id = NEW.child)
465+ THEN
466+ SELECT name INTO parent_name FROM packageset WHERE id = NEW.parent;
467+ SELECT name INTO child_name FROM packageset WHERE id = NEW.child;
468+ SELECT ds.name INTO parent_distroseries FROM packageset ps, distroseries ds WHERE ps.id = NEW.parent AND ps.distroseries = ds.id;
469+ SELECT ds.name INTO child_distroseries FROM packageset ps, distroseries ds WHERE ps.id = NEW.child AND ps.distroseries = ds.id;
470+ RAISE EXCEPTION 'Package sets % and % belong to different distro series (to % and % respectively) and thus cannot be associated.', child_name, parent_name, child_distroseries, parent_distroseries;
471+ END IF;
472+
473 IF EXISTS(
474 SELECT * FROM flatpackagesetinclusion
475 WHERE parent = NEW.child AND child = NEW.parent LIMIT 1)
476
477=== modified file 'database/schema/unautovacuumable.py'
478--- database/schema/unautovacuumable.py 2009-06-24 21:17:33 +0000
479+++ database/schema/unautovacuumable.py 2009-11-03 18:13:43 +0000
480@@ -19,6 +19,7 @@
481 # pylint: disable-msg=W0403
482 import _pythonpath
483
484+from distutils.version import LooseVersion
485 from optparse import OptionParser
486 import sys
487 import time
488@@ -44,14 +45,31 @@
489 con.set_isolation_level(0) # Autocommit
490 cur = con.cursor()
491
492+ cur.execute('show server_version')
493+ pg_version = LooseVersion(cur.fetchone()[0])
494+
495 log.debug("Disabling autovacuum on all tables in the database.")
496- cur.execute("""
497- INSERT INTO pg_autovacuum
498- SELECT pg_class.oid, FALSE, -1,-1,-1,-1,-1,-1,-1,-1
499- FROM pg_class
500- WHERE relkind in ('r','t')
501- AND pg_class.oid NOT IN (SELECT vacrelid FROM pg_autovacuum)
502- """)
503+ if pg_version < LooseVersion('8.4.0'):
504+ cur.execute("""
505+ INSERT INTO pg_autovacuum
506+ SELECT pg_class.oid, FALSE, -1,-1,-1,-1,-1,-1,-1,-1
507+ FROM pg_class
508+ WHERE relkind in ('r','t')
509+ AND pg_class.oid NOT IN (SELECT vacrelid FROM pg_autovacuum)
510+ """)
511+ else:
512+ cur.execute("""
513+ SELECT nspname,relname
514+ FROM pg_namespace, pg_class
515+ WHERE relnamespace = pg_namespace.oid
516+ AND relkind = 'r' AND nspname <> 'pg_catalog'
517+ """)
518+ for namespace, table in list(cur.fetchall()):
519+ cur.execute("""
520+ ALTER TABLE ONLY "%s"."%s" SET (
521+ autovacuum_enabled=false,
522+ toast.autovacuum_enabled=false)
523+ """ % (namespace, table))
524
525 log.debug("Killing existing autovacuum processes")
526 num_autovacuums = -1
527
528=== modified file 'lib/canonical/config/schema-lazr.conf'
529--- lib/canonical/config/schema-lazr.conf 2009-10-29 05:50:08 +0000
530+++ lib/canonical/config/schema-lazr.conf 2009-11-03 18:13:40 +0000
531@@ -1436,13 +1436,6 @@
532
533
534 [ppa.master]
535-# Line-separated repository lines to be used as build dependencies, in
536-# the following format:
537-# deb [user:pass@]<host>[/path] %(series)s[-pocket] [components]
538-# 'series' variable is replaced with the series name of the context build.
539-# datatype: string
540-dependencies: none
541-
542
543 [poimport]
544 # The database user which will be used by this process.
545
546=== modified file 'lib/canonical/ftests/pgsql.py'
547--- lib/canonical/ftests/pgsql.py 2009-06-25 05:30:52 +0000
548+++ lib/canonical/ftests/pgsql.py 2009-11-03 18:13:40 +0000
549@@ -144,6 +144,13 @@
550 host = None
551 port = None
552
553+ # Class attributes. With PostgreSQL 8.4, pg_shdepend bloats
554+ # hugely when we drop and create databases, because this
555+ # operation cancels any autovacuum process maintaining it.
556+ # To cope, we need to manually vacuum it ourselves occasionally.
557+ vacuum_shdepend_every = 10
558+ _vacuum_shdepend_counter = 0
559+
560 # (template, name) of last test. Class attribute.
561 _last_db = (None, None)
562 # Class attribute. True if we should destroy the DB because changes made.
563@@ -308,6 +315,10 @@
564 if 'does not exist' in str(x):
565 break
566 raise
567+ PgTestSetup._vacuum_shdepend_counter += 1
568+ if (PgTestSetup._vacuum_shdepend_counter
569+ % PgTestSetup.vacuum_shdepend_every) == 0:
570+ cur.execute('VACUUM pg_catalog.pg_shdepend')
571 finally:
572 con.close()
573
574
575=== modified file 'lib/canonical/launchpad/emailtemplates/branch-merge-proposal-created.txt'
576--- lib/canonical/launchpad/emailtemplates/branch-merge-proposal-created.txt 2009-08-05 19:27:17 +0000
577+++ lib/canonical/launchpad/emailtemplates/branch-merge-proposal-created.txt 2009-11-03 18:13:39 +0000
578@@ -1,4 +1,4 @@
579-%(proposal_registrant)s has proposed merging %(source_branch)s into %(target_branch)s.
580+%(proposal_registrant)s has proposed merging %(source_branch)s into %(target_branch)s%(prerequisite)s.
581
582 %(reviews)s%(related_bugs)s%(gap)s%(comment)s%(diff_cutoff_warning)s
583 --
584
585=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
586--- lib/canonical/launchpad/icing/style-3-0.css 2009-10-29 21:39:12 +0000
587+++ lib/canonical/launchpad/icing/style-3-0.css 2009-11-03 18:13:42 +0000
588@@ -466,6 +466,9 @@
589 padding: 0 1.5em 0 0;
590 }
591 .subordinate {
592+ margin-left: 2em;
593+ }
594+ol.subordinate {
595 margin-left: 4em;
596 }
597 .two-column-list li,
598
599=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
600--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-10-29 19:55:59 +0000
601+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-11-03 18:13:43 +0000
602@@ -234,6 +234,14 @@
603 IArchive, 'getAllPublishedBinaries', 'status', PackagePublishingStatus)
604 patch_choice_parameter_type(
605 IArchive, 'getAllPublishedBinaries', 'pocket', PackagePublishingPocket)
606+patch_plain_parameter_type(
607+ IArchive, 'isSourceUploadAllowed', 'distroseries', IDistroSeries)
608+patch_plain_parameter_type(
609+ IArchive, 'newPackagesetUploader', 'packageset', IPackageset)
610+patch_plain_parameter_type(
611+ IArchive, 'getUploadersForPackageset', 'packageset', IPackageset)
612+patch_plain_parameter_type(
613+ IArchive, 'deletePackagesetUploader', 'packageset', IPackageset)
614
615 # IDistribution
616 IDistribution['serieses'].value_type.schema = IDistroSeries
617@@ -283,6 +291,8 @@
618 IPackageset, 'getSourcesSharedBy', 'other_package_set', IPackageset)
619 patch_plain_parameter_type(
620 IPackageset, 'getSourcesNotSharedBy', 'other_package_set', IPackageset)
621+patch_collection_return_type(
622+ IPackageset, 'relatedSets', IPackageset)
623
624 # IPackageUpload
625 IPackageUpload['pocket'].vocabulary = PackagePublishingPocket
626
627=== modified file 'lib/canonical/launchpad/templates/launchpad-login.pt'
628--- lib/canonical/launchpad/templates/launchpad-login.pt 2009-10-16 16:13:00 +0000
629+++ lib/canonical/launchpad/templates/launchpad-login.pt 2009-11-03 18:13:39 +0000
630@@ -171,13 +171,13 @@
631 <p>
632 Creating your Launchpad account is easy. Here's what to do:</p>
633
634- <ol class="subordinate">
635- <li>Make sure cookies are enabled in your browser.</li>
636- <li>Enter your e-mail address and answer our random question
637- so we know that you're human.
638- </li>
639- <li>Follow the URL in the confirmation e-mail that Launchpad sends and you're done!</li>
640- </ol>
641+ <ol class="subordinate">
642+ <li>Make sure cookies are enabled in your browser.</li>
643+ <li>Enter your e-mail address and answer our random question
644+ so we know that you're human.
645+ </li>
646+ <li>Follow the URL in the confirmation e-mail that Launchpad sends and you're done!</li>
647+ </ol>
648
649
650
651
652=== modified file 'lib/lp/bugs/doc/externalbugtracker-debbugs.txt'
653--- lib/lp/bugs/doc/externalbugtracker-debbugs.txt 2009-10-06 07:38:29 +0000
654+++ lib/lp/bugs/doc/externalbugtracker-debbugs.txt 2009-11-03 18:13:40 +0000
655@@ -102,7 +102,7 @@
656 >>> from lp.bugs.scripts.checkwatches import BugWatchUpdater
657 >>> bug_watch_updater = BugWatchUpdater(txn)
658 >>> external_debbugs.sync_comments = False
659- >>> bug_watch_ids = [bug_watch.id for bug_watch in bug_watches]
660+ >>> bug_watch_ids = sorted([bug_watch.id for bug_watch in bug_watches])
661 >>> bug_watch_updater.updateBugWatches(external_debbugs, bug_watches)
662 INFO:...:Updating 5 watches for 5 bugs on http://...
663
664@@ -110,10 +110,10 @@
665 >>> for bug_watch_id in bug_watch_ids:
666 ... bug_watch = getUtility(IBugWatchSet).get(bug_watch_id)
667 ... print "%s: %s" % (bug_watch.remotebug, bug_watch.remotestatus)
668+ 280883: done grave woody security
669 304014: open important
670 327452: done critical patch security
671 327549: open important security
672- 280883: done grave woody security
673 308994: open important
674
675 The lastchecked attribute got updated for each bug watch, so no more
676
677=== modified file 'lib/lp/code/browser/branch.py'
678--- lib/lp/code/browser/branch.py 2009-10-11 02:33:30 +0000
679+++ lib/lp/code/browser/branch.py 2009-11-03 18:13:40 +0000
680@@ -575,7 +575,8 @@
681 BranchLifecycleStatus,
682 css_class_prefix='branchstatus'),
683 'status_value': self.context.lifecycle_status.title,
684- 'user_can_edit_status': check_permission('launchpad.Edit', self.context),
685+ 'user_can_edit_status': check_permission(
686+ 'launchpad.Edit', self.context),
687 'branch_path': '/' + self.context.unique_name,
688 })
689
690@@ -1153,6 +1154,13 @@
691 description=_(
692 "The branch that the source branch will be merged into."))
693
694+ prerequisite_branch = Choice(
695+ title=_('Prerequisite Branch'),
696+ vocabulary='Branch', required=False, readonly=False,
697+ description=_(
698+ 'A branch that should be merged before this one. (Its changes'
699+ ' will not be shown in the diff.)'))
700+
701 comment = Text(
702 title=_('Initial Comment'), required=False,
703 description=_('Describe your change.'))
704@@ -1206,6 +1214,7 @@
705 registrant = self.user
706 source_branch = self.context
707 target_branch = data['target_branch']
708+ prerequisite_branch = data.get('prerequisite_branch')
709
710 review_requests = []
711 reviewer = data.get('reviewer')
712@@ -1217,7 +1226,8 @@
713 # and an advanced expandable section.
714 proposal = source_branch.addLandingTarget(
715 registrant=registrant, target_branch=target_branch,
716- needs_review=True, initial_comment=data.get('comment'),
717+ prerequisite_branch=prerequisite_branch, needs_review=True,
718+ initial_comment=data.get('comment'),
719 review_requests=review_requests)
720
721 self.next_url = canonical_url(proposal)
722
723=== modified file 'lib/lp/code/mail/branchmergeproposal.py'
724--- lib/lp/code/mail/branchmergeproposal.py 2009-09-18 13:50:52 +0000
725+++ lib/lp/code/mail/branchmergeproposal.py 2009-11-03 18:13:41 +0000
726@@ -199,6 +199,7 @@
727 'proposal_registrant': self.merge_proposal.registrant.displayname,
728 'source_branch': self.merge_proposal.source_branch.bzr_identity,
729 'target_branch': self.merge_proposal.target_branch.bzr_identity,
730+ 'prerequisite': '',
731 'proposal_title': self.merge_proposal.title,
732 'proposal_url': canonical_url(self.merge_proposal),
733 'edit_subscription': '',
734@@ -209,6 +210,10 @@
735 'diff_cutoff_warning': '',
736 }
737
738+ if self.merge_proposal.prerequisite_branch is not None:
739+ prereq_url = self.merge_proposal.prerequisite_branch.bzr_identity
740+ params['prerequisite'] = ' with %s as a prerequisite' % prereq_url
741+
742 requested_reviews = []
743 for review in self.requested_reviews:
744 reviewer = review.reviewer
745
746=== modified file 'lib/lp/code/mail/tests/test_branchmergeproposal.py'
747--- lib/lp/code/mail/tests/test_branchmergeproposal.py 2009-09-18 13:50:52 +0000
748+++ lib/lp/code/mail/tests/test_branchmergeproposal.py 2009-11-03 18:13:42 +0000
749@@ -36,7 +36,7 @@
750 super(TestMergeProposalMailing, self).setUp('admin@canonical.com')
751
752 def makeProposalWithSubscriber(self, diff_text=None,
753- initial_comment=None):
754+ initial_comment=None, prerequisite=False):
755 if diff_text is not None:
756 preview_diff = PreviewDiff.create(
757 diff_text,
758@@ -49,9 +49,14 @@
759 registrant = self.factory.makePerson(
760 name='bazqux', displayname='Baz Qux', email='baz.qux@example.com')
761 product = self.factory.makeProduct(name='super-product')
762+ if prerequisite:
763+ prerequisite_branch = self.factory.makeProductBranch(product)
764+ else:
765+ prerequisite_branch = None
766 bmp = self.factory.makeBranchMergeProposal(
767- registrant=registrant, product=product, preview_diff=preview_diff,
768- initial_comment=initial_comment)
769+ registrant=registrant, product=product,
770+ prerequisite_branch=prerequisite_branch,
771+ preview_diff=preview_diff, initial_comment=initial_comment)
772 subscriber = self.factory.makePerson(displayname='Baz Quxx',
773 email='baz.quxx@example.com')
774 bmp.source_branch.subscribe(subscriber,
775@@ -133,6 +138,14 @@
776 'Requested reviews:\n Review-person (review-person)\n\n-- \n',
777 ctrl.body)
778
779+ def test_forCreation_with_prerequisite_branch(self):
780+ """Correctly format list of reviewers."""
781+ bmp, subscriber = self.makeProposalWithSubscriber(prerequisite=True)
782+ mailer = BMPMailer.forCreation(bmp, bmp.registrant)
783+ ctrl = mailer.generateEmail('baz.quxx@example.com', subscriber)
784+ prereq = bmp.prerequisite_branch.bzr_identity
785+ self.assertIn(' with %s as a prerequisite.' % prereq, ctrl.body)
786+
787 def test_to_addrs_includes_reviewers(self):
788 """The addresses for the to header include requested reviewers"""
789 request, requester = self.makeReviewRequest()
790
791=== modified file 'lib/lp/code/stories/branches/xx-branch-merge-proposals.txt'
792--- lib/lp/code/stories/branches/xx-branch-merge-proposals.txt 2009-10-23 02:36:17 +0000
793+++ lib/lp/code/stories/branches/xx-branch-merge-proposals.txt 2009-11-03 18:13:42 +0000
794@@ -57,6 +57,9 @@
795 >>> nopriv_browser.getControl(
796 ... name='field.target_branch.target_branch').value = (
797 ... '~name12/gnome-terminal/main')
798+ >>> nopriv_browser.getControl(
799+ ... name='field.prerequisite_branch').value = (
800+ ... '~name12/gnome-terminal/pushed')
801
802 There is a cancel link shown with the buttons.
803
804@@ -68,6 +71,20 @@
805 >>> print nopriv_browser.url
806 http://code.launchpad.dev/~name12/gnome-terminal/klingon/+merge/...
807
808+The summary reflects the selected target and prerequisite.
809+
810+ >>> def print_summary(browser):
811+ ... print extract_text(find_tag_by_id(
812+ ... browser.contents, 'proposal-summary'))
813+ >>> print_summary(nopriv_browser)
814+ Status:...
815+ Proposed branch:
816+ lp://dev/~name12/gnome-terminal/klingon
817+ Merge into:
818+ lp://dev/~name12/gnome-terminal/main
819+ Prerequisite:
820+ lp://dev/~name12/gnome-terminal/pushed
821+
822
823 Editing a commit message
824 ------------------------
825@@ -83,9 +100,6 @@
826 >>> print nopriv_browser.url
827 http://code.launchpad.dev/~name12/gnome-terminal/klingon/+merge/1
828
829- >>> def print_summary(browser):
830- ... print extract_text(find_tag_by_id(
831- ... browser.contents, 'proposal-summary'))
832 >>> print_tag_with_id(nopriv_browser.contents, 'edit-description')
833 Commit Message
834 Add more &lt;b&gt;mojo&lt;/b&gt;
835
836=== modified file 'lib/lp/code/templates/branchmergeproposal-pagelet-summary.pt'
837--- lib/lp/code/templates/branchmergeproposal-pagelet-summary.pt 2009-10-21 23:20:59 +0000
838+++ lib/lp/code/templates/branchmergeproposal-pagelet-summary.pt 2009-11-03 18:13:43 +0000
839@@ -105,6 +105,10 @@
840 <th>Merge into:</th>
841 <td tal:content="structure context/target_branch/fmt:bzr-link">lp:~foo/bar/baz</td>
842 </tr>
843+ <tr tal:condition="context/prerequisite_branch">
844+ <th>Prerequisite:</th>
845+ <td tal:content="structure context/prerequisite_branch/fmt:bzr-link">lp:~foo/bar/baz</td>
846+ </tr>
847 <tr tal:condition="context/preview_diff">
848 <th>Diff against target:</th>
849 <td>
850
851=== modified file 'lib/lp/code/templates/branchmergeproposal-resubmit.pt'
852--- lib/lp/code/templates/branchmergeproposal-resubmit.pt 2009-09-08 19:24:40 +0000
853+++ lib/lp/code/templates/branchmergeproposal-resubmit.pt 2009-11-03 18:13:39 +0000
854@@ -14,8 +14,9 @@
855 <div metal:fill-slot="extra_info">
856 <p>
857 Resubmitting this proposal to merge will cause this proposal to be
858- marked as <strong>superseded</strong>. Another proposal to merge with
859- the same source and target branches will be created.
860+ marked as <strong>superseded</strong>. Another merge proposal will
861+ be created, with the same source, target and prerequisite branch
862+ (if any).
863 </p>
864 <p>
865 Everyone who has reviewed the previous proposal or was requested to
866
867=== modified file 'lib/lp/registry/browser/__init__.py'
868--- lib/lp/registry/browser/__init__.py 2009-09-22 16:21:12 +0000
869+++ lib/lp/registry/browser/__init__.py 2009-11-03 18:13:42 +0000
870@@ -18,6 +18,8 @@
871
872 from zope.component import getUtility
873
874+from storm.store import Store
875+
876 from lp.bugs.interfaces.bugtask import BugTaskSearchParams, IBugTaskSet
877 from lp.registry.interfaces.productseries import IProductSeries
878 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
879@@ -136,15 +138,22 @@
880 """The context's URL."""
881 return canonical_url(self.context)
882
883- def _getBugtasks(self, milestone):
884- """Return the list `IBugTask`s targeted to the milestone."""
885- params = BugTaskSearchParams(milestone=milestone, user=None)
886+ def _getBugtasks(self, target):
887+ """Return the list `IBugTask`s associated with the target."""
888+ if IProductSeries.providedBy(target):
889+ params = BugTaskSearchParams(user=None)
890+ params.setProductSeries(target)
891+ else:
892+ params = BugTaskSearchParams(milestone=target, user=None)
893 bugtasks = getUtility(IBugTaskSet).search(params)
894 return list(bugtasks)
895
896- def _getSpecifications(self, milestone):
897- """Return the list `ISpecification`s targeted to the milestone."""
898- return list(milestone.specifications)
899+ def _getSpecifications(self, target):
900+ """Return the list `ISpecification`s associated to the target."""
901+ if IProductSeries.providedBy(target):
902+ return list(target.all_specifications)
903+ else:
904+ return list(target.specifications)
905
906 def _getProductRelease(self, milestone):
907 """The `IProductRelease` associated with the milestone."""
908@@ -158,10 +167,37 @@
909 else:
910 return []
911
912+ def _unsubscribe_structure(self, structure):
913+ """Removed the subscriptions from structure."""
914+ for subscription in structure.getSubscriptions():
915+ # The owner of the subscription or an admin are the only users
916+ # that can destroy a subscription, but this rule cannot prevent
917+ # the owner from removing the structure.
918+ Store.of(subscription).remove(subscription)
919+
920+ def _remove_series_bugs_and_specifications(self, series):
921+ """Untarget the associated bugs and subscriptions."""
922+ for spec in self._getSpecifications(series):
923+ spec.proposeGoal(None, self.user)
924+ for bugtask in self._getBugtasks(series):
925+ # Bugtasks cannot be deleted directly. In this case, the bugtask
926+ # is already reported on the product, so the series bugtask has
927+ # no purpose without a series.
928+ Store.of(bugtask).remove(bugtask)
929+
930 def _deleteProductSeries(self, series):
931- """Remove the series and delete/unlink related objects."""
932- # Delete all milestones, releases, and files.
933- # Any associated bugtasks and specifications are untargeted.
934+ """Remove the series and delete/unlink related objects.
935+
936+ All subordinate milestones, releases, and files will be deleted.
937+ Milestone bugs and blueprints will be untargeted.
938+ Series bugs and blueprints will be untargeted.
939+ Series and milestone structural subscriptions are unsubscribed.
940+ Series branches are unlinked.
941+ """
942+ self._unsubscribe_structure(series)
943+ self._remove_series_bugs_and_specifications(series)
944+ series.branch = None
945+
946 for milestone in series.all_milestones:
947 self._deleteMilestone(milestone)
948 # Series are not deleted because some objects like translations are
949@@ -174,6 +210,7 @@
950
951 def _deleteMilestone(self, milestone):
952 """Delete a milestone and unlink related objects."""
953+ self._unsubscribe_structure(milestone)
954 for bugtask in self._getBugtasks(milestone):
955 bugtask.milestone = None
956 for spec in self._getSpecifications(milestone):
957@@ -191,6 +228,7 @@
958
959 class RegistryEditFormView(LaunchpadEditFormView):
960 """A base class that provides consistent edit form behaviour."""
961+
962 @property
963 def page_title(self):
964 """The page title."""
965
966=== modified file 'lib/lp/registry/browser/productseries.py'
967--- lib/lp/registry/browser/productseries.py 2009-10-26 19:47:59 +0000
968+++ lib/lp/registry/browser/productseries.py 2009-11-03 18:13:42 +0000
969@@ -479,7 +479,7 @@
970 @cachedproperty
971 def bugtasks(self):
972 """A list of all `IBugTask`s targeted to this series."""
973- all_bugtasks = []
974+ all_bugtasks = self._getBugtasks(self.context)
975 for milestone in self.milestones:
976 all_bugtasks.extend(self._getBugtasks(milestone))
977 return all_bugtasks
978@@ -487,7 +487,7 @@
979 @cachedproperty
980 def specifications(self):
981 """A list of all `ISpecification`s targeted to this series."""
982- all_specifications = []
983+ all_specifications = self._getSpecifications(self.context)
984 for milestone in self.milestones:
985 all_specifications.extend(self._getSpecifications(milestone))
986 return all_specifications
987@@ -497,6 +497,11 @@
988 """Does the series have any targeted bugtasks or specifications."""
989 return len(self.bugtasks) > 0 or len(self.specifications) > 0
990
991+ @property
992+ def has_linked_branch(self):
993+ """Is the series linked to a branch."""
994+ return self.context.branch is not None
995+
996 @cachedproperty
997 def product_release_files(self):
998 """A list of all `IProductReleaseFile`s that belong to this series."""
999
1000=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
1001--- lib/lp/registry/browser/tests/milestone-views.txt 2009-09-22 16:21:12 +0000
1002+++ lib/lp/registry/browser/tests/milestone-views.txt 2009-11-03 18:13:41 +0000
1003@@ -654,6 +654,9 @@
1004 >>> bug = factory.makeBug(product=firefox)
1005 >>> bugtask = bug.bugtasks[0]
1006 >>> bugtask.milestone = milestone
1007+ >>> subscription = milestone.addSubscription(owner, owner)
1008+ >>> [subscription for subscription in owner.structural_subscriptions]
1009+ [<StructuralSubscription ...>]
1010
1011 >>> view = create_initialized_view(milestone, '+delete')
1012 >>> [bugtask.milestone.name for bugtask in view.bugtasks]
1013@@ -685,6 +688,9 @@
1014 >>> print bugtask.milestone
1015 None
1016
1017+ >>> [subscription for subscription in owner.structural_subscriptions]
1018+ []
1019+
1020 No Privileges Person cannot access this view because he is neither the
1021 project owner or series driver..
1022
1023
1024=== modified file 'lib/lp/registry/browser/tests/productseries-views.txt'
1025--- lib/lp/registry/browser/tests/productseries-views.txt 2009-10-23 16:21:47 +0000
1026+++ lib/lp/registry/browser/tests/productseries-views.txt 2009-11-03 18:13:42 +0000
1027@@ -229,6 +229,8 @@
1028 []
1029 >>> view.product_release_files
1030 []
1031+ >>> view.has_linked_branch
1032+ False
1033
1034 Most series that are deleted do not have any related objects, but a small
1035 portion do.
1036@@ -244,18 +246,38 @@
1037 >>> bugtask = bug.bugtasks[0]
1038 >>> bugtask.milestone = milestone_two
1039
1040+ >>> owner = product.owner
1041+ >>> series_specification = factory.makeSpecification(product=product)
1042+ >>> series_specification.proposeGoal(productseries, owner)
1043+ >>> series_bugtask = factory.makeBugTask(bug=bug, target=productseries)
1044+ >>> subscription = productseries.addSubscription(owner, owner)
1045+ >>> productseries.branch = factory.makeBranch()
1046+
1047 >>> view = create_view(productseries, name='+delete')
1048 >>> [milestone.name for milestone in view.milestones]
1049 [u'0.2', u'0.1']
1050 >>> view.has_bugtasks_and_specifications
1051 True
1052- >>> [bugtask.milestone.name for bugtask in view.bugtasks]
1053- [u'0.2']
1054- >>> [spec.milestone.name for spec in view.specifications]
1055- [u'0.1']
1056-
1057- # Listing and deleting product release files is done in the story
1058- # because they require the Librarian to be running.
1059+ >>> for bugtask in view.bugtasks:
1060+ ... if bugtask.milestone is not None:
1061+ ... print bugtask.milestone.name
1062+ ... else:
1063+ ... print bugtask.target.name
1064+ rabbit
1065+ 0.2
1066+ >>> for spec in view.specifications:
1067+ ... if spec.milestone is not None:
1068+ ... print spec.milestone.name
1069+ ... else:
1070+ ... print spec.goal.name
1071+ rabbit
1072+ 0.1
1073+
1074+ >>> view.has_linked_branch
1075+ True
1076+
1077+ # Listing and deleting product release files is done in
1078+ # product-release-views because they require the Librarian to be running.
1079
1080 Series that are the active focus of development cannot be deleted. The
1081 view's can_delete property checks this rule.
1082@@ -291,7 +313,8 @@
1083 Calling the view's delete action on a series that can be deleted will
1084 untarget the bugtasks and specifications that are targeted to the
1085 series' milestones. The milestones, releases, and release files are
1086-deleted.
1087+deleted. Bugs and blueprints targeted to the series are unassigned.
1088+Series structural subscriptions are removed. Branch links are removed.
1089
1090 >>> view = create_initialized_view(productseries, '+delete', form=form)
1091 >>> for notification in view.request.response.notifications:
1092@@ -308,11 +331,17 @@
1093 None
1094 >>> print bugtask.milestone
1095 None
1096+ >>> bugtask.related_tasks
1097+ []
1098+ >>> print series_specification.milestone
1099+ None
1100+ >>> [subscription for subscription in owner.structural_subscriptions]
1101+ []
1102
1103 The series was not actually deleted because there are problematic objects
1104 like translations. The series are assigned to the Obsolete Junk project.
1105 The series name is changed to 'product_name-series_name-date_created' to
1106-avoid conflicts.
1107+avoid conflicts. The linked branch is removed.
1108
1109 >>> from zope.component import getUtility
1110 >>> from canonical.launchpad.interfaces.launchpad import (
1111
1112=== modified file 'lib/lp/registry/doc/milestone.txt'
1113--- lib/lp/registry/doc/milestone.txt 2009-08-13 19:03:36 +0000
1114+++ lib/lp/registry/doc/milestone.txt 2009-11-03 18:13:40 +0000
1115@@ -496,3 +496,13 @@
1116 ...
1117 AssertionError: You cannot delete a milestone which has specifications
1118 targeted to it.
1119+
1120+If a milestone has a structural subscription, it cannot be deleted.
1121+
1122+ >>> milestone = ff_onedotzero.newMilestone('1.0.14')
1123+ >>> subscription = milestone.addSubscription(owner, owner)
1124+ >>> milestone.destroySelf()
1125+ Traceback (most recent call last):
1126+ ...
1127+ AssertionError: You cannot delete a milestone which has structural
1128+ subscriptions.
1129
1130=== modified file 'lib/lp/registry/model/distribution.py'
1131--- lib/lp/registry/model/distribution.py 2009-10-23 16:20:14 +0000
1132+++ lib/lp/registry/model/distribution.py 2009-11-03 18:13:43 +0000
1133@@ -851,6 +851,7 @@
1134 SourcePackagePublishingHistory.dateremoved is NULL
1135 """ % sqlvalues(self, archive),
1136 distinct=True,
1137+ orderBy="name",
1138 clauseTables=['SourcePackagePublishingHistory', 'DistroSeries',
1139 'SourcePackageRelease']))
1140
1141
1142=== modified file 'lib/lp/registry/model/milestone.py'
1143--- lib/lp/registry/model/milestone.py 2009-10-22 09:43:29 +0000
1144+++ lib/lp/registry/model/milestone.py 2009-11-03 18:13:43 +0000
1145@@ -188,6 +188,9 @@
1146 """See `IMilestone`."""
1147 params = BugTaskSearchParams(milestone=self, user=None)
1148 bugtasks = getUtility(IBugTaskSet).search(params)
1149+ assert len(self.getSubscriptions()) == 0, (
1150+ "You cannot delete a milestone which has structural "
1151+ "subscriptions.")
1152 assert bugtasks.count() == 0, (
1153 "You cannot delete a milestone which has bugtasks targeted "
1154 "to it.")
1155@@ -238,6 +241,7 @@
1156 """See lp.registry.interfaces.milestone.IMilestoneSet."""
1157 return Milestone.selectBy(active=True, orderBy='id')
1158
1159+
1160 class ProjectMilestone(HasBugsBase):
1161 """A virtual milestone implementation for project.
1162
1163
1164=== modified file 'lib/lp/registry/templates/product-index.pt'
1165--- lib/lp/registry/templates/product-index.pt 2009-10-08 15:54:09 +0000
1166+++ lib/lp/registry/templates/product-index.pt 2009-11-03 18:13:40 +0000
1167@@ -219,7 +219,7 @@
1168 </tal:release>
1169
1170 <p class="alternate">
1171- <a class="sprint info"
1172+ <a class="sprite info"
1173 tal:define="link overview_menu/downloads"
1174 tal:condition="release"
1175 tal:attributes="href link/fmt:url;
1176
1177=== modified file 'lib/lp/registry/templates/productseries-delete.pt'
1178--- lib/lp/registry/templates/productseries-delete.pt 2009-08-11 21:31:51 +0000
1179+++ lib/lp/registry/templates/productseries-delete.pt 2009-11-03 18:13:43 +0000
1180@@ -30,10 +30,12 @@
1181 </tal:no-files>
1182 </p>
1183
1184- <ul id="milestones" tal:condition="view/milestones">
1185+ <ul id="milestones" class="subordinate"
1186+ tal:condition="view/milestones">
1187 <li tal:repeat="milestone view/milestones">
1188 <strong>
1189- <a tal:attributes="href milestone/fmt:url"><tal:name
1190+ <a class="sprite milestone"
1191+ tal:attributes="href milestone/fmt:url"><tal:name
1192 content="milestone/name">0.9</tal:name><tal:codename
1193 condition="milestone/code_name">
1194 "<tal:name
1195@@ -42,9 +44,8 @@
1196 </li>
1197 </ul>
1198
1199-
1200-
1201- <ul id="files" tal:condition="view/product_release_files">
1202+ <ul id="files" class="subordinate"
1203+ tal:condition="view/product_release_files">
1204 <li tal:repeat="file view/product_release_files">
1205 <strong tal:content="file/libraryfile/filename">foo.tgz</strong>
1206 </li>
1207@@ -54,7 +55,7 @@
1208 The following bugs and blueprints will be <em>untargeted</em>:
1209 </p>
1210
1211- <ul id="bugtasks-and-blueprints"
1212+ <ul id="bugtasks-and-blueprints" class="subordinate"
1213 tal:condition="view/has_bugtasks_and_specifications">
1214 <li tal:repeat="bugtask view/bugtasks"
1215 tal:content="structure bugtask/bug/fmt:link">bug 1
1216@@ -64,6 +65,11 @@
1217 </li>
1218 </ul>
1219
1220+ <p tal:condition="view/has_linked_branch">
1221+ The associated branch will be <em>unlinked</em>:
1222+ <a tal:replace="structure view/context/branch/fmt:link" />
1223+ </p>
1224+
1225 <p>
1226 Series deletion is permanent.
1227 </p>
1228
1229=== modified file 'lib/lp/soyuz/adapters/archivedependencies.py'
1230--- lib/lp/soyuz/adapters/archivedependencies.py 2009-08-16 12:38:12 +0000
1231+++ lib/lp/soyuz/adapters/archivedependencies.py 2009-11-03 18:13:42 +0000
1232@@ -36,7 +36,6 @@
1233 'pocket_dependencies',
1234 ]
1235
1236-from canonical.config import config
1237 from lp.registry.interfaces.pocket import (
1238 PackagePublishingPocket, pocketsuffix)
1239 from lp.soyuz.interfaces.archive import ArchivePurpose, ALLOW_RELEASE_BUILDS
1240@@ -161,11 +160,9 @@
1241
1242 # Append external sources_list lines for this archive if it's
1243 # specified in the configuration.
1244- archive_config_key = 'ppa.%s_%s' % (
1245- build.archive.owner.name, build.archive.name)
1246- if archive_config_key in config:
1247- archive_config = config[archive_config_key]
1248- for archive_dep in archive_config.dependencies.splitlines():
1249+ dependencies = build.archive.external_dependencies
1250+ if dependencies is not None:
1251+ for archive_dep in dependencies.splitlines():
1252 line = archive_dep % (
1253 {'series': build.distroarchseries.distroseries.name})
1254 sources_list_lines.append(line)
1255
1256=== modified file 'lib/lp/soyuz/browser/archive.py'
1257--- lib/lp/soyuz/browser/archive.py 2009-10-30 12:29:32 +0000
1258+++ lib/lp/soyuz/browser/archive.py 2009-11-03 18:13:43 +0000
1259@@ -28,6 +28,7 @@
1260
1261 from datetime import datetime, timedelta
1262 import pytz
1263+from urlparse import urlparse
1264
1265 from zope.app.form.browser import TextAreaWidget
1266 from zope.component import getUtility
1267@@ -1775,7 +1776,10 @@
1268 class ArchiveAdminView(BaseArchiveEditView):
1269
1270 field_names = ['enabled', 'private', 'require_virtualized',
1271- 'buildd_secret', 'authorized_size', 'relative_build_score']
1272+ 'buildd_secret', 'authorized_size', 'relative_build_score',
1273+ 'external_dependencies']
1274+
1275+ custom_widget('external_dependencies', TextAreaWidget, height=3)
1276
1277 def validate_save(self, action, data):
1278 """Validate the save action on ArchiveAdminView.
1279@@ -1794,12 +1798,44 @@
1280 self.setFieldError(
1281 'private',
1282 'Private teams may not have public archives.')
1283-
1284 elif data.get('buildd_secret') is not None and not data['private']:
1285 self.setFieldError(
1286 'buildd_secret',
1287 'Do not specify for non-private archives')
1288
1289+ # Check the external_dependencies field.
1290+ ext_deps = data.get('external_dependencies')
1291+ if ext_deps is not None:
1292+ errors = self.validate_external_dependencies(ext_deps)
1293+ if len(errors) != 0:
1294+ error_text = "\n".join(errors)
1295+ self.setFieldError('external_dependencies', error_text)
1296+
1297+ def validate_external_dependencies(self, ext_deps):
1298+ """Validate the external_dependencies field.
1299+
1300+ :param ext_deps: The dependencies form field to check.
1301+ """
1302+ errors = []
1303+ # The field can consist of multiple entries separated by
1304+ # newlines, so process each in turn.
1305+ for dep in ext_deps.splitlines():
1306+ try:
1307+ deb, url, suite, components = dep.split(" ", 3)
1308+ except ValueError:
1309+ errors.append(
1310+ "'%s' is not a complete and valid sources.list entry"
1311+ % dep)
1312+ continue
1313+
1314+ if deb != "deb":
1315+ errors.append("%s: Must start with 'deb'" % dep)
1316+ url_components = urlparse(url)
1317+ if not url_components[0] or not url_components[1]:
1318+ errors.append("%s: Invalid URL" % dep)
1319+
1320+ return errors
1321+
1322 @property
1323 def owner_is_private_team(self):
1324 """Is the owner a private team?
1325
1326=== modified file 'lib/lp/soyuz/browser/configure.zcml'
1327--- lib/lp/soyuz/browser/configure.zcml 2009-10-22 10:33:00 +0000
1328+++ lib/lp/soyuz/browser/configure.zcml 2009-11-03 18:13:42 +0000
1329@@ -783,7 +783,7 @@
1330 />
1331 <browser:url
1332 for="lp.soyuz.interfaces.packageset.IPackageset"
1333- path_expression="name"
1334+ path_expression="string:${distroseries/name}/${name}"
1335 parent_utility="lp.soyuz.interfaces.packageset.IPackagesetSet"
1336 />
1337 <browser:url
1338
1339=== modified file 'lib/lp/soyuz/browser/packageset.py'
1340--- lib/lp/soyuz/browser/packageset.py 2009-06-30 16:56:07 +0000
1341+++ lib/lp/soyuz/browser/packageset.py 2009-11-03 18:13:42 +0000
1342@@ -17,3 +17,22 @@
1343 class PackagesetSetNavigation(GetitemNavigation):
1344 """Navigation methods for PackagesetSet."""
1345 usedfor = IPackagesetSet
1346+
1347+ def traverse(self, distroseries):
1348+ """Traverse package sets in distro series context.
1349+
1350+ The URI fragment of interest is:
1351+
1352+ /package-sets/lucid/mozilla
1353+
1354+ where 'lucid' is the distro series and 'mozilla' is the package set
1355+ *name* respectively.
1356+ """
1357+ if self.request.stepstogo:
1358+ # The package set name follows after the distro series.
1359+ ps_name = self.request.stepstogo.consume()
1360+ return self.context.getByName(ps_name, distroseries=distroseries)
1361+
1362+ # Otherwise return None (to trigger a NotFound error).
1363+ return None
1364+
1365
1366=== modified file 'lib/lp/soyuz/browser/tests/archive-views.txt'
1367--- lib/lp/soyuz/browser/tests/archive-views.txt 2009-10-30 21:42:10 +0000
1368+++ lib/lp/soyuz/browser/tests/archive-views.txt 2009-11-03 18:13:41 +0000
1369@@ -1345,3 +1345,50 @@
1370
1371 >>> print copy.status.name
1372 ACCEPTED
1373+
1374+
1375+== External dependencies validation ==
1376+
1377+The ArchiveAdminView checks the external_dependencies form data to see if
1378+it's a valid sources.list entry.
1379+
1380+ >>> ppa_archive_view = create_initialized_view(
1381+ ... cprov.archive, name="+admin")
1382+
1383+The validate_external_dependencies() method is called when validating and will
1384+return a list of errors if the data dis not validate. A valid entry is of the
1385+form:
1386+ deb scheme://domain/ suite component[s]
1387+
1388+ >>> print ppa_archive_view.validate_external_dependencies(
1389+ ... "deb http://example.com/ karmic main")
1390+ []
1391+
1392+Multiple entries are valid, separated by newlines:
1393+
1394+ >>> print ppa_archive_view.validate_external_dependencies(
1395+ ... "deb http://example.com/ karmic main\n"
1396+ ... "deb http://example.com/ karmic restricted")
1397+ []
1398+
1399+If the line does not start with the word "deb" it fails:
1400+
1401+ >>> print ppa_archive_view.validate_external_dependencies(
1402+ ... "deb http://example.com/ karmic universe\n"
1403+ ... "dab http://example.com/ karmic main")
1404+ ["dab http://example.com/ karmic main: Must start with 'deb'"]
1405+
1406+If the line has too few parts it fails. Here we're missing a suite:
1407+
1408+ >>> print ppa_archive_view.validate_external_dependencies(
1409+ ... "deb http://example.com/ karmic universe\n"
1410+ ... "deb http://example.com/ main")
1411+ ["'deb http://example.com/ main'
1412+ is not a complete and valid sources.list entry"]
1413+
1414+If the URL looks invalid, it fails:
1415+
1416+ >>> print ppa_archive_view.validate_external_dependencies(
1417+ ... "deb http://example.com/ karmic universe\n"
1418+ ... "deb example.com/ karmic main")
1419+ ['deb example.com/ karmic main: Invalid URL']
1420
1421=== modified file 'lib/lp/soyuz/configure.zcml'
1422--- lib/lp/soyuz/configure.zcml 2009-10-28 14:42:40 +0000
1423+++ lib/lp/soyuz/configure.zcml 2009-11-03 18:13:42 +0000
1424@@ -399,8 +399,8 @@
1425 <require
1426 permission="launchpad.Commercial"
1427 set_attributes="authorized_size buildd_secret
1428- enabled private require_virtualized
1429- relative_build_score"/>
1430+ enabled external_dependencies private
1431+ require_virtualized relative_build_score "/>
1432 <require
1433 permission="launchpad.Admin"
1434 set_attributes="distribution name signing_key"/>
1435@@ -856,4 +856,12 @@
1436 new"/>
1437 </securedutility>
1438
1439+ <!-- PackagesetGroup -->
1440+ <class
1441+ class="lp.soyuz.model.packagesetgroup.PackagesetGroup">
1442+ <allow
1443+ interface="lp.soyuz.interfaces.packagesetgroup.IPackagesetGroup"/>
1444+ </class>
1445+
1446+
1447 </configure>
1448
1449=== modified file 'lib/lp/soyuz/doc/archive-dependencies.txt'
1450--- lib/lp/soyuz/doc/archive-dependencies.txt 2009-08-28 07:34:44 +0000
1451+++ lib/lp/soyuz/doc/archive-dependencies.txt 2009-11-03 18:13:40 +0000
1452@@ -441,38 +441,27 @@
1453
1454 == External build dependencies ==
1455
1456-Via a static configuration change, any PPA hosted in launchpad can be
1457+Via an administrator change, any PPA hosted in launchpad can be
1458 assigned to one or more 'external' build dependencies additionally to
1459 the internal ones.
1460
1461-There is a configuration category called 'ppa' which can be extended
1462-for any hosted PPA. They are named as following:
1463-
1464- [ppa.<owner_name>_<ppa_name>]
1465-
1466-The 'ppa' categories contain the 'dependencies' field, which is a
1467-multi-line string listing the external dependencies in the debian
1468-sources_list format.
1469-
1470- deb [user:pass@]<host>[/path] %(series)s[-pocket] [components]
1471+There is a column on IArchive called 'external_dependencies' which can be set
1472+for any hosted PPA. It is a string listing the comma-separated external
1473+dependencies in the debian sources_list format.
1474+
1475+ deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] [components]
1476
1477 The '%(series)s' part is optional and will be replaced on-the-fly with
1478 the series name for the build record being dispatched.
1479
1480-We will create a configuration for Celso's PPA.
1481-
1482- >>> from canonical.config import config
1483- >>> cprov_deps = """
1484- ... [ppa.cprov_ppa]
1485- ... dependencies:
1486- ... deb http://user:pass@repository zoing everything
1487- ... deb http://user:pass@repository %(series)s public private
1488- ... deb http://user:pass@repository %(series)s-extra public
1489- ... """
1490- >>> config.push('test_deps', cprov_deps)
1491-
1492-Configuration in place, now builds in Celso's PPA will use the
1493-external dependencies.
1494+We will create some dependencies for Celso's PPA.
1495+
1496+ >>> cprov.archive.external_dependencies = (
1497+ ... "deb http://user:pass@repository zoing everything\n"
1498+ ... "deb http://user:pass@repository %(series)s public private\n"
1499+ ... "deb http://user:pass@repository %(series)s-extra public")
1500+
1501+Now builds in Celso's PPA will use the external dependencies.
1502
1503 >>> print_building_sources_list(a_build)
1504 deb http://ftpmaster.internal/ubuntu hoary
1505@@ -486,6 +475,3 @@
1506 deb http://user:pass@repository hoary-extra public
1507 deb http://user:pass@repository zoing everything
1508
1509-Tests done, we can remove the extra configuration content we've added.
1510-
1511- >>> unused = config.pop('test_deps')
1512
1513=== modified file 'lib/lp/soyuz/doc/archive.txt'
1514--- lib/lp/soyuz/doc/archive.txt 2009-10-13 16:12:10 +0000
1515+++ lib/lp/soyuz/doc/archive.txt 2009-11-03 18:13:41 +0000
1516@@ -58,11 +58,11 @@
1517 >>> cprov_archive.failed_count
1518 1
1519
1520-relative_build_score is a property that can be set only by LP admins and read
1521-by anyone. It is a signed integer that represents a delta to all the build
1522-scores for builds done in the archive.
1523+relative_build_score and external_dependencies are properties that can be set
1524+only by LP admins and read by anyone.
1525
1526-The default value is zero:
1527+relative_build_score is a signed integer that represents a delta to all the
1528+build scores for builds done in the archive. The default value is zero:
1529
1530 >>> cprov_archive.relative_build_score
1531 0
1532@@ -74,10 +74,28 @@
1533 ...
1534 Unauthorized: (..., 'relative_build_score', 'launchpad.Commercial')
1535
1536-As a Launchpad admin, it will work.
1537+external_dependencies is a text field that should contain a comma-separated
1538+list of sources.list entries in the format:
1539+deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] [components]
1540+where the series variable is replaced with the series name of the context
1541+build. This allows an admin to set external repositories as a source for
1542+build dependencies on the context PPA. Its default value is None:
1543+
1544+ >>> print cprov_archive.external_dependencies
1545+ None
1546+
1547+Amending it as an unprivileged user results in failure:
1548+
1549+ >>> cprov_archive.external_dependencies = "test"
1550+ Traceback (most recent call last):
1551+ ...
1552+ Unauthorized: (..., 'external_dependencies', 'launchpad.Commercial')
1553+
1554+As a Launchpad admin, setting these properties will work.
1555
1556 >>> login("admin@canonical.com")
1557 >>> cprov_archive.relative_build_score = 100
1558+ >>> cprov_archive.external_dependencies = "test"
1559
1560 The buildd_secret is used by the slave scanner when generating a
1561 sources.list entry for the builder to access a private archive. It is
1562
1563=== modified file 'lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt'
1564--- lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt 2009-09-04 12:17:11 +0000
1565+++ lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt 2009-11-03 18:13:40 +0000
1566@@ -209,6 +209,7 @@
1567
1568 >>> ubuntu.updateCompleteSourcePackageCache(
1569 ... archive=cprov.archive, ztm=LaunchpadZopelessLayer.txn, log=TestLog())
1570+ DEBUG: ...
1571 DEBUG: Considering source 'pmount'
1572 ...
1573
1574
1575=== modified file 'lib/lp/soyuz/doc/package-cache.txt'
1576--- lib/lp/soyuz/doc/package-cache.txt 2009-08-13 13:09:34 +0000
1577+++ lib/lp/soyuz/doc/package-cache.txt 2009-11-03 18:13:39 +0000
1578@@ -163,11 +163,12 @@
1579
1580 >>> updates = ubuntu.updateCompleteSourcePackageCache(
1581 ... archive=ubuntu.main_archive, ztm=transaction, log=TestLog())
1582- DEBUG: Considering source 'mozilla-firefox'
1583- ...
1584+ DEBUG: ...
1585 DEBUG: Considering source 'cdrkit'
1586 DEBUG: Creating new source cache entry.
1587 ...
1588+ DEBUG: Considering source 'mozilla-firefox'
1589+ ...
1590
1591 >>> print updates
1592 10
1593@@ -355,6 +356,7 @@
1594
1595 >>> source_updates = ubuntu.updateCompleteSourcePackageCache(
1596 ... archive=cprov.archive, ztm=transaction, log=TestLog())
1597+ DEBUG: ...
1598 DEBUG: Considering source 'pmount'
1599 ...
1600
1601
1602=== modified file 'lib/lp/soyuz/interfaces/archive.py'
1603--- lib/lp/soyuz/interfaces/archive.py 2009-10-26 09:43:56 +0000
1604+++ lib/lp/soyuz/interfaces/archive.py 2009-11-03 18:13:41 +0000
1605@@ -256,6 +256,18 @@
1606 description=_(
1607 "A delta to apply to all build scores for this archive."))
1608
1609+ external_dependencies = Text(
1610+ title=_("External dependencies"), required=False, readonly=False,
1611+ description=_(
1612+ "Newline-separated list of repositories to be used to retrieve "
1613+ "any external build dependencies when building packages in this "
1614+ "archive, in the format:\n"
1615+ "deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] "
1616+ "[components]\n"
1617+ "The series variable is replaced with the series name of the "
1618+ "context build.\n"
1619+ "NOTE: This is for migration of OEM PPAs only!"))
1620+
1621 def getSourcesForDeletion(name=None, status=None, distroseries=None):
1622 """All `ISourcePackagePublishingHistory` available for deletion.
1623
1624@@ -433,8 +445,10 @@
1625
1626 @operation_parameters(
1627 person=Reference(schema=IPerson),
1628- packageset=TextLine(
1629- title=_("Package set"), required=True),
1630+ # Really IPackageset, corrected in _schema_circular_imports to avoid
1631+ # circular import.
1632+ packageset=Reference(
1633+ Interface, title=_("Package set"), required=True),
1634 explicit=Bool(
1635 title=_("Explicit"), required=False))
1636 # Really IArchivePermission, set in _schema_circular_imports to avoid
1637@@ -444,7 +458,7 @@
1638 """Add a package set based permission for a person.
1639
1640 :param person: An `IPerson` for whom you want to add permission.
1641- :param packageset: An `IPackageset` or a string package set name.
1642+ :param packageset: An `IPackageset`.
1643 :param explicit: True if the package set in question requires
1644 specialist skills for proper handling.
1645
1646@@ -453,8 +467,10 @@
1647 """
1648
1649 @operation_parameters(
1650- packageset=TextLine(
1651- title=_("Package set"), required=True),
1652+ # Really IPackageset, corrected in _schema_circular_imports to avoid
1653+ # circular import.
1654+ packageset=Reference(
1655+ Interface, title=_("Package set"), required=True),
1656 direct_permissions=Bool(
1657 title=_("Ignore package set hierarchy"), required=False))
1658 # Really IArchivePermission, set in _schema_circular_imports to avoid
1659@@ -464,7 +480,7 @@
1660 def getUploadersForPackageset(packageset, direct_permissions=True):
1661 """The `ArchivePermission` records for uploaders to the package set.
1662
1663- :param packageset: An `IPackageset` or a string package set name.
1664+ :param packageset: An `IPackageset`.
1665 :param direct_permissions: If True, only consider permissions granted
1666 directly for the package set at hand. Otherwise, include any
1667 uploaders for package sets that include this one.
1668@@ -475,8 +491,10 @@
1669
1670 @operation_parameters(
1671 person=Reference(schema=IPerson),
1672- packageset=TextLine(
1673- title=_("Package set"), required=True),
1674+ # Really IPackageset, corrected in _schema_circular_imports to avoid
1675+ # circular import.
1676+ packageset=Reference(
1677+ Interface, title=_("Package set"), required=True),
1678 explicit=Bool(
1679 title=_("Explicit"), required=False))
1680 @export_write_operation()
1681@@ -484,7 +502,7 @@
1682 """Revoke upload permissions for a person.
1683
1684 :param person: An `IPerson` for whom you want to revoke permission.
1685- :param packageset: An `IPackageset` or a string package set name.
1686+ :param packageset: An `IPackageset`.
1687 :param explicit: The value of the 'explicit' flag for the permission
1688 to be revoked.
1689 """
1690@@ -567,9 +585,13 @@
1691 @operation_parameters(
1692 sourcepackagename=TextLine(
1693 title=_("Source package name"), required=True),
1694- person=Reference(schema=IPerson))
1695+ person=Reference(schema=IPerson),
1696+ distroseries=Reference(
1697+ # Really IDistroSeries, avoiding a circular import here.
1698+ Interface,
1699+ title=_("The distro series"), required=False))
1700 @export_read_operation()
1701- def isSourceUploadAllowed(sourcepackagename, person):
1702+ def isSourceUploadAllowed(sourcepackagename, person, distroseries=None):
1703 """True if the person is allowed to upload the given source package.
1704
1705 Return True if there exists a permission that combines
1706@@ -585,6 +607,9 @@
1707 either a string or a `ISourcePackageName`.
1708 :param person: An `IPerson` for whom you want to find out which
1709 package sets he has access to.
1710+ :param distroseries: The `IDistroSeries` for which to check
1711+ permissions. If none is supplied then `currentseries` in
1712+ Ubuntu is assumed.
1713
1714 :raises NoSuchSourcePackageName: if a source package with the
1715 given name could not be found.
1716
1717=== modified file 'lib/lp/soyuz/interfaces/archivepermission.py'
1718--- lib/lp/soyuz/interfaces/archivepermission.py 2009-08-03 17:10:12 +0000
1719+++ lib/lp/soyuz/interfaces/archivepermission.py 2009-11-03 18:13:41 +0000
1720@@ -258,7 +258,8 @@
1721 archive in question.
1722 """
1723
1724- def isSourceUploadAllowed(archive, sourcepackagename, person):
1725+ def isSourceUploadAllowed(
1726+ archive, sourcepackagename, person, distroseries=None):
1727 """True if the person is allowed to upload the given source package.
1728
1729 Return True if there exists a permission that combines
1730@@ -275,6 +276,9 @@
1731 either a string or a `ISourcePackageName`.
1732 :param person: An `IPerson` for whom you want to find out which
1733 package sets he has access to.
1734+ :param distroseries: The `IDistroSeries` for which to check
1735+ permissions. If none is supplied then `currentseries` in
1736+ Ubuntu is assumed.
1737
1738 :raises SourceNotFound: if a source package with the given
1739 name could not be found.
1740@@ -284,6 +288,9 @@
1741 def uploadersForPackageset(archive, packageset, direct_permissions=True):
1742 """The `ArchivePermission` records for uploaders to the package set.
1743
1744+ Please note: if a package set *name* is passed the respective
1745+ package set in the current distro series will be used.
1746+
1747 :param archive: The archive the permission applies to.
1748 :param packageset: An `IPackageset` or a string package set name.
1749 :param direct_permissions: If True only consider permissions granted
1750@@ -332,6 +339,9 @@
1751 def newPackagesetUploader(archive, person, packageset, explicit=False):
1752 """Create and return a new `ArchivePermission` for an uploader.
1753
1754+ Please note: if a package set *name* is passed the respective
1755+ package set in the current distro series will be used.
1756+
1757 :param archive: The archive the permission applies to.
1758 :param person: An `IPerson` for whom you want to add permission.
1759 :param packageset: An `IPackageset` or a string package set name.
1760@@ -379,6 +389,9 @@
1761 def deletePackagesetUploader(archive, person, packageset, explicit=False):
1762 """Revoke upload permissions for a person.
1763
1764+ Please note: if a package set *name* is passed the respective
1765+ package set in the current distro series will be used.
1766+
1767 :param archive: The archive the permission applies to.
1768 :param person: An `IPerson` for whom you want to revoke permission.
1769 :param packageset: An `IPackageset` or a string package set name.
1770
1771=== modified file 'lib/lp/soyuz/interfaces/packageset.py'
1772--- lib/lp/soyuz/interfaces/packageset.py 2009-07-25 16:33:39 +0000
1773+++ lib/lp/soyuz/interfaces/packageset.py 2009-11-03 18:13:41 +0000
1774@@ -8,6 +8,7 @@
1775 __metaclass__ = type
1776
1777 __all__ = [
1778+ 'DuplicatePackagesetName',
1779 'IPackageset',
1780 'IPackagesetSet',
1781 'NoSuchPackageSet',
1782@@ -26,14 +27,23 @@
1783 operation_parameters, operation_returns_collection_of,
1784 operation_returns_entry, webservice_error)
1785 from lazr.restful.fields import Reference
1786+from lp.registry.interfaces.distroseries import IDistroSeries
1787 from lp.registry.interfaces.person import IPerson
1788 from lp.registry.interfaces.role import IHasOwner
1789+from lp.soyuz.interfaces.packagesetgroup import IPackagesetGroup
1790
1791
1792 class NoSuchPackageSet(NameLookupFailed):
1793 """Raised when we try to look up an PackageSet that doesn't exist."""
1794- webservice_error(400) #Bad request.
1795- _message_prefix = "No such packageset"
1796+ # Bad request.
1797+ webservice_error(400)
1798+ _message_prefix = "No such package set (in the specified distro series)"
1799+
1800+
1801+class DuplicatePackagesetName(Exception):
1802+ """Raised for packagesets with the same name and distroseries."""
1803+ # Bad request.
1804+ webservice_error(400)
1805
1806
1807 class IPackagesetViewOnly(IHasOwner):
1808@@ -48,7 +58,7 @@
1809
1810 owner = exported(Reference(
1811 IPerson, title=_("Person"), required=True, readonly=True,
1812- description=_("The person who owns the package set at hand.")))
1813+ description=_("The person who owns this package set.")))
1814
1815 name = exported(TextLine(
1816 title=_('Valid package set name'),
1817@@ -58,6 +68,18 @@
1818 title=_("Description"), required=True, readonly=True,
1819 description=_("The description for the package set at hand.")))
1820
1821+ distroseries = exported(Reference(
1822+ IDistroSeries, title=_("Distribution series"), required=True,
1823+ readonly=True,
1824+ description=_(
1825+ "The distroseries to which this package set is related.")))
1826+
1827+ packagesetgroup = Reference(
1828+ IPackagesetGroup, title=_('Package set group'), required=True,
1829+ readonly=True,
1830+ description=_(
1831+ 'Used internally to link package sets across distro series.'))
1832+
1833 def sourcesIncluded(direct_inclusion=False):
1834 """Get all source names associated with this package set.
1835
1836@@ -195,6 +217,16 @@
1837 names.
1838 """
1839
1840+ @operation_returns_collection_of(Interface)
1841+ @export_read_operation()
1842+ def relatedSets():
1843+ """Get all package sets related to this one.
1844+
1845+ Return all package sets that are related to this one.
1846+
1847+ :return: A (potentially empty) sequence of `IPackageset` instances.
1848+ """
1849+
1850
1851 class IPackagesetEdit(Interface):
1852 """A writeable interface for package sets."""
1853@@ -316,15 +348,31 @@
1854 title=_('Package set description'), required=True),
1855 owner=Reference(
1856 IPerson, title=_("Person"), required=True, readonly=True,
1857- description=_("The person who owns the package set at hand.")))
1858+ description=_("The person who owns this package set.")),
1859+ distroseries=Reference(
1860+ IDistroSeries, title=_("Distroseries"), required=False,
1861+ readonly=True, description=_(
1862+ "The distribution series to which the packageset "
1863+ "is related.")),
1864+ related_set=Reference(
1865+ IPackageset, title=_("Related package set"), required=False,
1866+ readonly=True, description=_(
1867+ "The new package set will share the package set group "
1868+ "with this one.")))
1869 @export_factory_operation(IPackageset, [])
1870- def new(name, description, owner):
1871+ def new(name, description, owner, distroseries=None, related_set=None):
1872 """Create a new package set.
1873
1874 :param name: the name of the package set to be created.
1875 :param description: the description for the package set to be created.
1876 :param owner: the owner of the package set to be created.
1877+ :param distroseries: the distroseries to which the new packageset
1878+ is related. Defaults to the current Ubuntu series.
1879+ :param related_set: the newly created package set is to be related to
1880+ `related_set` (by being placed in the same package group).
1881
1882+ :raises DuplicatePackagesetName: if a package set with the same `name`
1883+ exists in `distroseries` already.
1884 :return: a newly created `IPackageset`.
1885 """
1886
1887@@ -332,10 +380,12 @@
1888 name=TextLine(title=_('Package set name'), required=True))
1889 @operation_returns_entry(IPackageset)
1890 @export_read_operation()
1891- def getByName(name):
1892+ def getByName(name, distroseries=None):
1893 """Return the single package set with the given name (if any).
1894
1895 :param name: the name of the package set sought.
1896+ :param distroseries: the distroseries to which the new packageset
1897+ is related. Defaults to the current Ubuntu series.
1898
1899 :return: An `IPackageset` instance or None.
1900 """
1901
1902=== added file 'lib/lp/soyuz/interfaces/packagesetgroup.py'
1903--- lib/lp/soyuz/interfaces/packagesetgroup.py 1970-01-01 00:00:00 +0000
1904+++ lib/lp/soyuz/interfaces/packagesetgroup.py 2009-11-03 18:13:43 +0000
1905@@ -0,0 +1,41 @@
1906+# Copyright 2009 Canonical Ltd. This software is licensed under the
1907+# GNU Affero General Public License version 3 (see the file LICENSE).
1908+
1909+"""Packageset Group interface."""
1910+
1911+__metaclass__ = type
1912+
1913+__all__ = [
1914+ 'IPackagesetGroup',
1915+ ]
1916+
1917+from zope.schema import Datetime, Int
1918+
1919+from lazr.restful.fields import Reference
1920+
1921+from canonical.launchpad import _
1922+from lp.registry.interfaces.person import IPerson
1923+from lp.registry.interfaces.role import IHasOwner
1924+
1925+
1926+class IPackagesetGroup(IHasOwner):
1927+ """A group of related package sets across distroseries'
1928+
1929+ This class is used internally to group related packagesets across
1930+ distroseries. For example, if in Karmic there is a 'gnome-games'
1931+ package set, and this package set is cloned initially for Lucid,
1932+ then both packagesets would refer to the same packageset-group.
1933+
1934+ Packageset-groups are not exposed at all. The date_created and
1935+ owner fields are present for internal use only.
1936+ """
1937+ id = Int(title=_('ID'), required=True, readonly=True)
1938+
1939+ date_created = Datetime(
1940+ title=_("Date Created"), required=True, readonly=True,
1941+ description=_("The creation date/time for this packageset group."))
1942+
1943+ owner = Reference(
1944+ IPerson, title=_("Person"), required=True, readonly=True,
1945+ description=_("The person who created this packageset group."))
1946+
1947
1948=== modified file 'lib/lp/soyuz/model/archive.py'
1949--- lib/lp/soyuz/model/archive.py 2009-10-26 09:43:56 +0000
1950+++ lib/lp/soyuz/model/archive.py 2009-11-03 18:13:42 +0000
1951@@ -174,6 +174,12 @@
1952 relative_build_score = IntCol(
1953 dbName='relative_build_score', notNull=True, default=0)
1954
1955+ # This field is specifically and only intended for OEM migration to
1956+ # Launchpad and should be re-examined in October 2010 to see if it
1957+ # is still relevant.
1958+ external_dependencies = StringCol(
1959+ dbName='external_dependencies', notNull=False, default=None)
1960+
1961 def _init(self, *args, **kw):
1962 """Provide the right interface for URL traversal."""
1963 SQLBase._init(self, *args, **kw)
1964@@ -998,11 +1004,12 @@
1965 return permission_set.packagesetsForSource(
1966 self, sourcepackagename, direct_permissions)
1967
1968- def isSourceUploadAllowed(self, sourcepackagename, person):
1969+ def isSourceUploadAllowed(
1970+ self, sourcepackagename, person, distroseries=None):
1971 """See `IArchive`."""
1972 permission_set = getUtility(IArchivePermissionSet)
1973 return permission_set.isSourceUploadAllowed(
1974- self, sourcepackagename, person)
1975+ self, sourcepackagename, person, distroseries)
1976
1977 def getFileByName(self, filename):
1978 """See `IArchive`."""
1979
1980=== modified file 'lib/lp/soyuz/model/archivepermission.py'
1981--- lib/lp/soyuz/model/archivepermission.py 2009-07-28 21:52:56 +0000
1982+++ lib/lp/soyuz/model/archivepermission.py 2009-11-03 18:13:43 +0000
1983@@ -22,6 +22,7 @@
1984 from canonical.database.enumcol import EnumCol
1985 from canonical.database.sqlbase import sqlvalues, SQLBase
1986
1987+from lp.registry.interfaces.distribution import IDistributionSet
1988 from lp.soyuz.interfaces.archive import ComponentNotFound
1989 from lp.soyuz.interfaces.archivepermission import (
1990 ArchivePermissionType, IArchivePermission, IArchivePermissionSet,
1991@@ -312,9 +313,13 @@
1992 def _nameToPackageset(self, packageset):
1993 """Helper to convert a possible string name to IPackageset."""
1994 if isinstance(packageset, basestring):
1995+ # A package set name was passed, assume the current distro series.
1996+ ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
1997 name = packageset
1998 store = IStore(Packageset)
1999- packageset = store.find(Packageset, name=name).one()
2000+ packageset = store.find(
2001+ Packageset, name=name,
2002+ distroseries=ubuntu.currentseries).one()
2003 if packageset is not None:
2004 return packageset
2005 else:
2006@@ -475,48 +480,56 @@
2007 ''', (sourcepackagename.id, archive.id)))
2008 return rset
2009
2010- def isSourceUploadAllowed(self, archive, sourcepackagename, person):
2011+ def isSourceUploadAllowed(
2012+ self, archive, sourcepackagename, person, distroseries=None):
2013 """See `IArchivePermissionSet`."""
2014 sourcepackagename = self._nameToSourcePackageName(sourcepackagename)
2015 store = IStore(ArchivePermission)
2016+ if distroseries is None:
2017+ ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
2018+ distroseries = ubuntu.currentseries
2019
2020 # Put together the parameters for the query that follows.
2021 archive_params = (ArchivePermissionType.UPLOAD, archive.id)
2022+ permission_params = (sourcepackagename.id, person.id, distroseries.id)
2023 query_params = (
2024 # Query parameters for the first WHERE clause.
2025- (archive.id, sourcepackagename.id) +
2026+ (archive.id, distroseries.id, sourcepackagename.id) +
2027 # Query parameters for the second WHERE clause.
2028- (sourcepackagename.id,) + (person.id,) + archive_params +
2029+ permission_params + archive_params +
2030 # Query parameters for the third WHERE clause.
2031- (sourcepackagename.id,) + (person.id,) + archive_params)
2032+ permission_params + archive_params)
2033
2034 query = '''
2035 SELECT CASE
2036 WHEN (
2037 SELECT COUNT(ap.id)
2038- FROM packagesetsources pss, archivepermission ap
2039+ FROM packagesetsources pss, archivepermission ap, packageset ps
2040 WHERE
2041 ap.archive = %s AND ap.explicit = TRUE
2042+ AND ap.packageset = ps.id AND ps.distroseries = %s
2043 AND pss.sourcepackagename = %s
2044 AND pss.packageset = ap.packageset) > 0
2045 THEN (
2046 SELECT COUNT(ap.id)
2047 FROM
2048- packagesetsources pss, archivepermission ap,
2049+ packagesetsources pss, archivepermission ap, packageset ps,
2050 teamparticipation tp
2051 WHERE
2052 pss.sourcepackagename = %s
2053 AND ap.person = tp.team AND tp.person = %s
2054+ AND ap.packageset = ps.id AND ps.distroseries = %s
2055 AND pss.packageset = ap.packageset AND ap.explicit = TRUE
2056 AND ap.permission = %s AND ap.archive = %s)
2057 ELSE (
2058 SELECT COUNT(ap.id)
2059 FROM
2060- packagesetsources pss, archivepermission ap,
2061+ packagesetsources pss, archivepermission ap, packageset ps,
2062 teamparticipation tp, flatpackagesetinclusion fpsi
2063 WHERE
2064 pss.sourcepackagename = %s
2065 AND ap.person = tp.team AND tp.person = %s
2066+ AND ap.packageset = ps.id AND ps.distroseries = %s
2067 AND pss.packageset = fpsi.child AND fpsi.parent = ap.packageset
2068 AND ap.permission = %s AND ap.archive = %s)
2069 END AS number_of_permitted_package_sets;
2070
2071=== modified file 'lib/lp/soyuz/model/packageset.py'
2072--- lib/lp/soyuz/model/packageset.py 2009-07-25 16:33:39 +0000
2073+++ lib/lp/soyuz/model/packageset.py 2009-11-03 18:13:42 +0000
2074@@ -6,6 +6,7 @@
2075
2076 import pytz
2077
2078+from storm.exceptions import IntegrityError
2079 from storm.expr import In, SQL
2080 from storm.locals import DateTime, Int, Reference, Storm, Unicode
2081
2082@@ -13,12 +14,14 @@
2083 from zope.interface import implements
2084
2085 from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore
2086+from canonical.launchpad.webapp.interfaces import NotFoundError
2087+from lp.registry.interfaces.distribution import IDistributionSet
2088 from lp.registry.interfaces.sourcepackagename import (
2089 ISourcePackageName, ISourcePackageNameSet)
2090 from lp.registry.model.sourcepackagename import SourcePackageName
2091 from lp.soyuz.interfaces.packageset import (
2092- IPackageset, IPackagesetSet, NoSuchPackageSet)
2093-
2094+ DuplicatePackagesetName, IPackageset, IPackagesetSet, NoSuchPackageSet)
2095+from lp.soyuz.model.packagesetgroup import PackagesetGroup
2096
2097 def _order_result_set(result_set):
2098 """Default order for package set and source package name result sets."""
2099@@ -45,6 +48,12 @@
2100 name = Unicode(name='name', allow_none=False)
2101 description = Unicode(name='description', allow_none=False)
2102
2103+ distroseries_id = Int(name='distroseries', allow_none=False)
2104+ distroseries = Reference(distroseries_id, 'DistroSeries.id')
2105+
2106+ packagesetgroup_id = Int(name='packagesetgroup', allow_none=False)
2107+ packagesetgroup = Reference(packagesetgroup_id, 'PackagesetGroup.id')
2108+
2109 def add(self, data):
2110 """See `IPackageset`."""
2111 handlers = (
2112@@ -279,41 +288,101 @@
2113
2114 def addSubsets(self, names):
2115 """See `IPackageset`."""
2116- clauses = (Packageset, In(Packageset.name, names))
2117+ clauses = (
2118+ Packageset, In(Packageset.name, names),
2119+ Packageset.distroseries == self.distroseries)
2120 self._api_add_or_remove(clauses, self._addDirectSuccessors)
2121
2122 def removeSubsets(self, names):
2123 """See `IPackageset`."""
2124- clauses = (Packageset, In(Packageset.name, names))
2125+ clauses = (
2126+ Packageset, In(Packageset.name, names),
2127+ Packageset.distroseries == self.distroseries)
2128 self._api_add_or_remove(clauses, self._removeDirectSuccessors)
2129
2130+ def relatedSets(self):
2131+ """See `IPackageset`."""
2132+ store = IStore(Packageset)
2133+ result_set = store.find(
2134+ Packageset,
2135+ Packageset.packagesetgroup == self.packagesetgroup,
2136+ Packageset.id != self.id)
2137+ return _order_result_set(result_set)
2138+
2139
2140 class PackagesetSet:
2141 """See `IPackagesetSet`."""
2142 implements(IPackagesetSet)
2143
2144- def new(self, name, description, owner):
2145+ def new(
2146+ self, name, description, owner, distroseries=None, related_set=None):
2147 """See `IPackagesetSet`."""
2148 store = IMasterStore(Packageset)
2149+
2150+ packagesetgroup = None
2151+ if related_set is not None:
2152+ # Use the packagesetgroup of the `related_set`.
2153+ packagesetgroup = related_set.packagesetgroup
2154+ else:
2155+ # We create the related internal PackagesetGroup for this
2156+ # packageset so that we can later see related package sets across
2157+ # distroserieses.
2158+ packagesetgroup = PackagesetGroup()
2159+ packagesetgroup.owner = owner
2160+ store.add(packagesetgroup)
2161+
2162+ if distroseries is None:
2163+ ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
2164+ distroseries = ubuntu.currentseries
2165+
2166 packageset = Packageset()
2167+ packageset.packagesetgroup = packagesetgroup
2168 packageset.name = name
2169 packageset.description = description
2170 packageset.owner = owner
2171+
2172+ packageset.distroseries = distroseries
2173+
2174 store.add(packageset)
2175+
2176+ # We need to ensure that the cached statements are flushed so that
2177+ # the duplicate name constraint gets triggered here.
2178+ try:
2179+ store.flush()
2180+ except IntegrityError:
2181+ raise DuplicatePackagesetName()
2182+
2183 return packageset
2184
2185 def __getitem__(self, name):
2186 """See `IPackagesetSet`."""
2187 return self.getByName(name)
2188
2189- def getByName(self, name):
2190+ def getByName(self, name, distroseries=None):
2191 """See `IPackagesetSet`."""
2192 store = IStore(Packageset)
2193 if not isinstance(name, unicode):
2194 name = unicode(name, 'utf-8')
2195- package_set = store.find(Packageset, Packageset.name == name).one()
2196+
2197+ ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
2198+ extra_args = []
2199+ if distroseries is not None:
2200+ # If the user just passed a distro series name, look it up.
2201+ if isinstance(distroseries, basestring):
2202+ try:
2203+ distroseries = ubuntu[distroseries]
2204+ except NotFoundError:
2205+ raise NoSuchPackageSet(distroseries)
2206+ extra_args.append(Packageset.distroseries == distroseries)
2207+ else:
2208+ extra_args.append(Packageset.distroseries == ubuntu.currentseries)
2209+
2210+ package_set = store.find(
2211+ Packageset, Packageset.name == name, *extra_args).one()
2212+
2213 if package_set is None:
2214 raise NoSuchPackageSet(name)
2215+
2216 return package_set
2217
2218 def getByOwner(self, owner):
2219
2220=== added file 'lib/lp/soyuz/model/packagesetgroup.py'
2221--- lib/lp/soyuz/model/packagesetgroup.py 1970-01-01 00:00:00 +0000
2222+++ lib/lp/soyuz/model/packagesetgroup.py 2009-11-03 18:13:41 +0000
2223@@ -0,0 +1,30 @@
2224+# Copyright 2009 Canonical Ltd. This software is licensed under the
2225+# GNU Affero General Public License version 3 (see the file LICENSE).
2226+
2227+__metaclass__ = type
2228+
2229+__all__ = [
2230+ 'PackagesetGroup',
2231+ ]
2232+
2233+import pytz
2234+
2235+from storm.locals import DateTime, Int, Reference, Storm
2236+
2237+from zope.interface import implements
2238+
2239+from lp.soyuz.interfaces.packagesetgroup import IPackagesetGroup
2240+
2241+
2242+class PackagesetGroup(Storm):
2243+ """See `IPackageset`."""
2244+ implements(IPackagesetGroup)
2245+ __storm_table__ = 'PackagesetGroup'
2246+ id = Int(primary=True)
2247+
2248+ date_created = DateTime(
2249+ name='date_created', allow_none=False, tzinfo=pytz.UTC)
2250+
2251+ owner_id = Int(name='owner', allow_none=False)
2252+ owner = Reference(owner_id, 'Person.id')
2253+
2254
2255=== modified file 'lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt'
2256--- lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt 2009-10-13 10:05:58 +0000
2257+++ lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt 2009-11-03 18:13:40 +0000
2258@@ -354,6 +354,7 @@
2259 * Set a maximum disk size: uploads will be rejected if the resulting
2260 PPA size is exceeding the authorized size.
2261 * Set a per-archive build score delta.
2262+ * Set external archive dependencies
2263
2264 In this case, the administrator may wish to amend the PPA so that it is
2265 set up like the ubuntu security PPA, which is private but does not
2266@@ -367,6 +368,8 @@
2267 True
2268 >>> admin_browser.getControl(name="field.relative_build_score").value
2269 '0'
2270+ >>> admin_browser.getControl(name="field.external_dependencies").value
2271+ ''
2272
2273 >>> admin_browser.getControl(name="field.enabled").value = False
2274 >>> admin_browser.getControl(name="field.private").value = True
2275@@ -376,6 +379,9 @@
2276 >>> admin_browser.getControl(name="field.authorized_size").value = '1'
2277 >>> admin_browser.getControl(
2278 ... name="field.relative_build_score").value = '199'
2279+ >>> admin_browser.getControl(
2280+ ... name="field.external_dependencies"
2281+ ... ).value = "deb http://my.spethial.repo.com/ %(series)s main"
2282 >>> admin_browser.getControl("Save").click()
2283
2284 Once confirmed the administrator is sent to the PPA page where he can
2285@@ -388,16 +394,33 @@
2286 ... print msg
2287 This archive has been disabled.
2288
2289-We need go back to the "Administer archive" page to see the build score change
2290-that was made:
2291+We need go back to the "Administer archive" page to see the build score and
2292+external dependencies changes that were made:
2293
2294 >>> admin_browser.getLink("Administer archive").click()
2295 >>> admin_browser.getControl(name="field.relative_build_score").value
2296 '199'
2297+ >>> admin_browser.getControl(name="field.external_dependencies").value
2298+ 'deb http://my.spethial.repo.com/ %(series)s main'
2299+
2300+The external dependencies field is validated to make sure it looks like
2301+a sources.list entry. If the field fails validation an error is displayed.
2302+
2303+ >>> admin_browser.getControl(
2304+ ... name="field.external_dependencies"
2305+ ... ).value = "deb not_a_url"
2306+ >>> admin_browser.getControl("Save").click()
2307+ >>> for error in get_feedback_messages(admin_browser.contents):
2308+ ... print error
2309+ There is 1 error.
2310+ 'deb not_a_url' is not a complete and valid sources.list entry
2311+
2312
2313 When the archive is private, the buildd secret must also be set, or an
2314 error is issued:
2315
2316+ >>> admin_browser.getControl(
2317+ ... name="field.external_dependencies").value = ""
2318 >>> admin_browser.getControl(name="field.private").value = True
2319 >>> admin_browser.getControl(name="field.buildd_secret").value = ""
2320 >>> admin_browser.getControl("Save").click()
2321
2322=== modified file 'lib/lp/soyuz/stories/webservice/xx-packageset.txt'
2323--- lib/lp/soyuz/stories/webservice/xx-packageset.txt 2009-08-20 04:46:48 +0000
2324+++ lib/lp/soyuz/stories/webservice/xx-packageset.txt 2009-11-03 18:13:41 +0000
2325@@ -18,6 +18,9 @@
2326 Please refer to the tests contained in the file above if you are really
2327 interested in package sets and the complete functionality they offer.
2328
2329+
2330+== General package set properties ==
2331+
2332 We start off by creating an 'umbrella' package set that will include all
2333 source packages.
2334
2335@@ -52,16 +55,16 @@
2336 Can we access it via the webservice API as well?
2337
2338 >>> logout()
2339- >>> umbrella = webservice.get("/package-sets/umbrella").jsonBody()
2340+ >>> umbrella = webservice.get("/package-sets/hoary/umbrella").jsonBody()
2341 >>> print umbrella['self_link']
2342- http://api.launchpad.dev/beta/package-sets/umbrella
2343+ http://api.launchpad.dev/beta/package-sets/hoary/umbrella
2344
2345 `PackageSet`s can be looked up by name.
2346
2347 >>> response = webservice.named_get(
2348 ... '/package-sets', 'getByName', {}, name=u'umbrella')
2349 >>> print response.jsonBody()['self_link']
2350- http://api.launchpad.dev/beta/package-sets/umbrella
2351+ http://api.launchpad.dev/beta/package-sets/hoary/umbrella
2352
2353 When a `PackageSet` cannot be found, an error is returned.
2354
2355@@ -70,9 +73,17 @@
2356 >>> print response
2357 HTTP/1.1 400 Bad Request
2358 ...
2359- NoSuchPackageSet: No such packageset: 'not-found'.
2360+ No such package set (in the specified distro series): 'not-found'.
2361+ ...
2362 <BLANKLINE>
2363
2364+Here's an example with a funny URL concoted by a "smart" user.
2365+
2366+ >>> response = webservice.get("/package-sets/lucid-plus-1/umbrella/+pwn")
2367+ >>> print response
2368+ HTTP/1.1 404 Not Found
2369+ ...
2370+
2371 Populate the 'umbrella' package set with source packages.
2372
2373 >>> from canonical.launchpad.webapp.interfaces import (
2374@@ -81,7 +92,7 @@
2375 >>> store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
2376 >>> all_spns = store.find(SourcePackageName)
2377 >>> response = webservice.named_post(
2378- ... '/package-sets/umbrella', 'addSources', {},
2379+ ... '/package-sets/hoary/umbrella', 'addSources', {},
2380 ... names=[spn.name for spn in all_spns])
2381 >>> print response
2382 HTTP/1.1 200 Ok
2383@@ -91,7 +102,7 @@
2384 exist will not fail. Non-existing source package names are *ignored*.
2385
2386 >>> response = webservice.named_post(
2387- ... '/package-sets/umbrella', 'addSources', {},
2388+ ... '/package-sets/hoary/umbrella', 'addSources', {},
2389 ... names=[u'does-not-exist'])
2390 >>> print response
2391 HTTP/1.1 200 Ok
2392@@ -99,7 +110,7 @@
2393 null
2394
2395 >>> response = webservice.named_post(
2396- ... '/package-sets/umbrella', 'removeSources', {},
2397+ ... '/package-sets/hoary/umbrella', 'removeSources', {},
2398 ... names=[u'does-not-exist'])
2399 >>> print response
2400 HTTP/1.1 200 Ok
2401@@ -109,7 +120,7 @@
2402 Let's see what we got.
2403
2404 >>> response = webservice.named_get(
2405- ... '/package-sets/umbrella', 'getSourcesIncluded', {})
2406+ ... '/package-sets/hoary/umbrella', 'getSourcesIncluded', {})
2407 >>> print response
2408 HTTP/1.1 200 Ok
2409 ...
2410@@ -136,7 +147,7 @@
2411 from the 'umbrella' package set.
2412
2413 >>> response = webservice.named_post(
2414- ... '/package-sets/umbrella', 'removeSources', {},
2415+ ... '/package-sets/hoary/umbrella', 'removeSources', {},
2416 ... names=["foobar", "iceweasel"])
2417 >>> print response
2418 HTTP/1.1 200 Ok
2419@@ -146,7 +157,7 @@
2420 from the list below.
2421
2422 >>> response = webservice.named_get(
2423- ... '/package-sets/umbrella', 'getSourcesIncluded', {})
2424+ ... '/package-sets/hoary/umbrella', 'getSourcesIncluded', {})
2425 >>> print response
2426 HTTP/1.1 200 Ok
2427 ...
2428@@ -176,13 +187,13 @@
2429
2430 >>> response = webservice.get("/package-sets/")
2431 >>> print_payload(response)
2432- http://api.launchpad.dev/beta/package-sets/umbrella
2433+ http://api.launchpad.dev/beta/package-sets/hoary/umbrella
2434
2435 Package sets may include other package sets (as subsets). At this point,
2436 however, we only have the 'umbrella' package set. It hence has no subsets.
2437
2438 >>> response = webservice.named_get(
2439- ... '/package-sets/umbrella', 'setsIncluded', {})
2440+ ... '/package-sets/hoary/umbrella', 'setsIncluded', {})
2441 >>> print response
2442 HTTP/1.1 200 Ok
2443 ...
2444@@ -206,6 +217,50 @@
2445 HTTP/1.1 201 Created
2446 ...
2447
2448+
2449+=== Package sets and distro series ===
2450+
2451+Every package set is associated with a distro series.
2452+
2453+ >>> from lazr.restful.testing.webservice import pprint_entry
2454+ >>> mozilla = webservice.named_get(
2455+ ... '/package-sets', 'getByName', {}, name=u'mozilla').jsonBody()
2456+ >>> print mozilla['distroseries_link']
2457+ http://api.launchpad.dev/beta/ubuntu/hoary
2458+
2459+
2460+=== Related package sets ===
2461+
2462+When adding a package set we can specify that is to be related to another set
2463+that exists already.
2464+
2465+ >>> grumpy = webservice.get("/ubuntu/grumpy").jsonBody()
2466+ >>> print grumpy['self_link']
2467+ http://api.launchpad.dev/beta/ubuntu/grumpy
2468+
2469+We are adding a new 'mozilla' package set to the 'grumpy' distro series and
2470+it is related to 'mozilla' in 'hoary'.
2471+
2472+ >>> response = webservice.named_post(
2473+ ... '/package-sets', 'new', {},
2474+ ... name=u'mozilla',
2475+ ... description=u'Contains all mozilla packages in grumpy',
2476+ ... owner=name12['self_link'], distroseries=grumpy['self_link'],
2477+ ... related_set=mozilla['self_link'])
2478+ >>> print response
2479+ HTTP/1.1 201 Created
2480+ ...
2481+
2482+ >>> response = webservice.named_get(
2483+ ... mozilla['self_link'], 'relatedSets', {})
2484+ >>> print_payload(response)
2485+ http://api.launchpad.dev/beta/package-sets/grumpy/mozilla
2486+
2487+
2488+== Package set hierarchy ==
2489+
2490+More package sets are needed to set up the hierarchy described below.
2491+
2492 >>> response = webservice.named_post(
2493 ... '/package-sets', 'new', {},
2494 ... name=u'firefox', description=u'Contains all firefox packages',
2495@@ -232,6 +287,18 @@
2496 HTTP/1.1 201 Created
2497 ...
2498
2499+The 'languagepack' package set will be removed later (in hoary). Let's add a
2500+set with the same name in 'grumpy' to make sure that the right one is found.
2501+
2502+ >>> response = webservice.named_post(
2503+ ... '/package-sets', 'new', {},
2504+ ... name=u'languagepack',
2505+ ... description=u'Contains all languagepack packages',
2506+ ... owner=name12['self_link'], distroseries=grumpy['self_link'])
2507+ >>> print response
2508+ HTTP/1.1 201 Created
2509+ ...
2510+
2511 In order to test whether methods relating to package set hierarchies were
2512 exposed on the Launchpad API correctly we will define the following package
2513 set hierarchy:
2514@@ -245,27 +312,27 @@
2515 * languagepack
2516
2517 >>> response = webservice.named_post(
2518- ... '/package-sets/umbrella', 'addSubsets', {},
2519+ ... '/package-sets/hoary/umbrella', 'addSubsets', {},
2520 ... names=[u'gnome', u'mozilla'])
2521 >>> print response
2522 HTTP/1.1 200 Ok
2523 ...
2524
2525 >>> response = webservice.named_post(
2526- ... '/package-sets/gnome', 'addSubsets', {}, names=[u'languagepack'])
2527+ ... '/package-sets/hoary/gnome', 'addSubsets', {}, names=[u'languagepack'])
2528 >>> print response
2529 HTTP/1.1 200 Ok
2530 ...
2531
2532 >>> response = webservice.named_post(
2533- ... '/package-sets/thunderbird', 'addSubsets', {},
2534+ ... '/package-sets/hoary/thunderbird', 'addSubsets', {},
2535 ... names=[u'languagepack'])
2536 >>> print response
2537 HTTP/1.1 200 Ok
2538 ...
2539
2540 >>> response = webservice.named_post(
2541- ... '/package-sets/mozilla', 'addSubsets', {},
2542+ ... '/package-sets/hoary/mozilla', 'addSubsets', {},
2543 ... names=[u'firefox', u'thunderbird'])
2544 >>> print response
2545 HTTP/1.1 200 Ok
2546@@ -275,7 +342,7 @@
2547 non-existing package sets will not fail.
2548
2549 >>> response = webservice.named_post(
2550- ... '/package-sets/thunderbird', 'addSubsets', {},
2551+ ... '/package-sets/hoary/thunderbird', 'addSubsets', {},
2552 ... names=[u'does-not-exist'])
2553 >>> print response
2554 HTTP/1.1 200 Ok
2555@@ -283,7 +350,7 @@
2556 null
2557
2558 >>> response = webservice.named_post(
2559- ... '/package-sets/thunderbird', 'removeSubsets', {},
2560+ ... '/package-sets/hoary/thunderbird', 'removeSubsets', {},
2561 ... names=[u'does-not-exist'])
2562 >>> print response
2563 HTTP/1.1 200 Ok
2564@@ -293,49 +360,49 @@
2565 The 'umbrella' package set should have plenty of subsets now.
2566
2567 >>> response = webservice.named_get(
2568- ... '/package-sets/umbrella', 'setsIncluded', {})
2569+ ... '/package-sets/hoary/umbrella', 'setsIncluded', {})
2570 >>> print_payload(response)
2571- http://api.launchpad.dev/beta/package-sets/firefox
2572- http://api.launchpad.dev/beta/package-sets/gnome
2573- http://api.launchpad.dev/beta/package-sets/languagepack
2574- http://api.launchpad.dev/beta/package-sets/mozilla
2575- http://api.launchpad.dev/beta/package-sets/thunderbird
2576+ http://api.launchpad.dev/beta/package-sets/hoary/firefox
2577+ http://api.launchpad.dev/beta/package-sets/hoary/gnome
2578+ http://api.launchpad.dev/beta/package-sets/hoary/languagepack
2579+ http://api.launchpad.dev/beta/package-sets/hoary/mozilla
2580+ http://api.launchpad.dev/beta/package-sets/hoary/thunderbird
2581
2582 However only two of the above are direct subsets.
2583
2584 >>> response = webservice.named_get(
2585- ... '/package-sets/umbrella', 'setsIncluded', {},
2586+ ... '/package-sets/hoary/umbrella', 'setsIncluded', {},
2587 ... direct_inclusion=True)
2588 >>> print_payload(response)
2589- http://api.launchpad.dev/beta/package-sets/gnome
2590- http://api.launchpad.dev/beta/package-sets/mozilla
2591+ http://api.launchpad.dev/beta/package-sets/hoary/gnome
2592+ http://api.launchpad.dev/beta/package-sets/hoary/mozilla
2593
2594 Let's ask the question the other way around what package sets are including
2595 a particular subset?
2596
2597 >>> response = webservice.named_get(
2598- ... '/package-sets/languagepack', 'setsIncludedBy', {})
2599+ ... '/package-sets/hoary/languagepack', 'setsIncludedBy', {})
2600 >>> print_payload(response)
2601- http://api.launchpad.dev/beta/package-sets/gnome
2602- http://api.launchpad.dev/beta/package-sets/mozilla
2603- http://api.launchpad.dev/beta/package-sets/thunderbird
2604- http://api.launchpad.dev/beta/package-sets/umbrella
2605+ http://api.launchpad.dev/beta/package-sets/hoary/gnome
2606+ http://api.launchpad.dev/beta/package-sets/hoary/mozilla
2607+ http://api.launchpad.dev/beta/package-sets/hoary/thunderbird
2608+ http://api.launchpad.dev/beta/package-sets/hoary/umbrella
2609
2610 The list of package sets that *directly* include 'languagepack' will be
2611 shorter because the transitive closure is ignored.
2612
2613 >>> response = webservice.named_get(
2614- ... '/package-sets/languagepack', 'setsIncludedBy', {},
2615+ ... '/package-sets/hoary/languagepack', 'setsIncludedBy', {},
2616 ... direct_inclusion=True)
2617 >>> print_payload(response)
2618- http://api.launchpad.dev/beta/package-sets/gnome
2619- http://api.launchpad.dev/beta/package-sets/thunderbird
2620+ http://api.launchpad.dev/beta/package-sets/hoary/gnome
2621+ http://api.launchpad.dev/beta/package-sets/hoary/thunderbird
2622
2623 We can remove subsets as well. In the example below 'thunderbird' will
2624 stop including 'languagepack'.
2625
2626 >>> response = webservice.named_post(
2627- ... '/package-sets/thunderbird', 'removeSubsets', {},
2628+ ... '/package-sets/hoary/thunderbird', 'removeSubsets', {},
2629 ... names=[u'languagepack'])
2630 >>> print response
2631 HTTP/1.1 200 Ok
2632@@ -344,37 +411,37 @@
2633 And, here we go, now 'languagepack' has only one direct predecessor: 'gnome'.
2634
2635 >>> response = webservice.named_get(
2636- ... '/package-sets/languagepack', 'setsIncludedBy', {},
2637+ ... '/package-sets/hoary/languagepack', 'setsIncludedBy', {},
2638 ... direct_inclusion=True)
2639 >>> print_payload(response)
2640- http://api.launchpad.dev/beta/package-sets/gnome
2641+ http://api.launchpad.dev/beta/package-sets/hoary/gnome
2642
2643 Let's add a few source packages to the 'firefox' and the 'thunderbird'
2644 package sets.
2645
2646 >>> response = webservice.named_post(
2647- ... '/package-sets/firefox', 'addSources', {},
2648+ ... '/package-sets/hoary/firefox', 'addSources', {},
2649 ... names=['at', 'mozilla-firefox', 'language-pack-de'])
2650 >>> print response
2651 HTTP/1.1 200 Ok
2652 ...
2653
2654 >>> response = webservice.named_get(
2655- ... '/package-sets/firefox', 'getSourcesIncluded', {})
2656+ ... '/package-sets/hoary/firefox', 'getSourcesIncluded', {})
2657 >>> print response
2658 HTTP/1.1 200 Ok
2659 ...
2660 ["at", "language-pack-de", "mozilla-firefox"]
2661
2662 >>> response = webservice.named_post(
2663- ... '/package-sets/thunderbird', 'addSources', {},
2664+ ... '/package-sets/hoary/thunderbird', 'addSources', {},
2665 ... names=['at', 'cnews', 'thunderbird', 'language-pack-de'])
2666 >>> print response
2667 HTTP/1.1 200 Ok
2668 ...
2669
2670 >>> response = webservice.named_get(
2671- ... '/package-sets/thunderbird', 'getSourcesIncluded', {})
2672+ ... '/package-sets/hoary/thunderbird', 'getSourcesIncluded', {})
2673 >>> print response
2674 HTTP/1.1 200 Ok
2675 ...
2676@@ -386,9 +453,9 @@
2677 ... '/package-sets/', 'setsIncludingSource', {},
2678 ... sourcepackagename=u'mozilla-firefox')
2679 >>> print_payload(response)
2680- http://api.launchpad.dev/beta/package-sets/firefox
2681- http://api.launchpad.dev/beta/package-sets/mozilla
2682- http://api.launchpad.dev/beta/package-sets/umbrella
2683+ http://api.launchpad.dev/beta/package-sets/hoary/firefox
2684+ http://api.launchpad.dev/beta/package-sets/hoary/mozilla
2685+ http://api.launchpad.dev/beta/package-sets/hoary/umbrella
2686
2687 Which package sets include the 'mozilla-firefox' source package *directly*?
2688
2689@@ -397,8 +464,8 @@
2690 ... sourcepackagename=u'mozilla-firefox',
2691 ... direct_inclusion=True)
2692 >>> print_payload(response)
2693- http://api.launchpad.dev/beta/package-sets/firefox
2694- http://api.launchpad.dev/beta/package-sets/umbrella
2695+ http://api.launchpad.dev/beta/package-sets/hoary/firefox
2696+ http://api.launchpad.dev/beta/package-sets/hoary/umbrella
2697
2698 If a non-existing source package name is passed it returns an error.
2699
2700@@ -414,9 +481,9 @@
2701 What source packages are shared by the 'firefox' and the 'thunderbird'
2702 package sets?
2703
2704- >>> thunderbird = webservice.get("/package-sets/thunderbird").jsonBody()
2705+ >>> thunderbird = webservice.get("/package-sets/hoary/thunderbird").jsonBody()
2706 >>> response = webservice.named_get(
2707- ... '/package-sets/firefox', 'getSourcesSharedBy', {},
2708+ ... '/package-sets/hoary/firefox', 'getSourcesSharedBy', {},
2709 ... other_package_set=thunderbird['self_link'])
2710 >>> print response
2711 HTTP/1.1 200 Ok
2712@@ -426,16 +493,16 @@
2713 How about the complement set i.e. the packages not shared?
2714
2715 >>> response = webservice.named_get(
2716- ... '/package-sets/firefox', 'getSourcesNotSharedBy', {},
2717+ ... '/package-sets/hoary/firefox', 'getSourcesNotSharedBy', {},
2718 ... other_package_set=thunderbird['self_link'])
2719 >>> print response
2720 HTTP/1.1 200 Ok
2721 ...
2722 ["mozilla-firefox"]
2723
2724- >>> firefox = webservice.get("/package-sets/firefox").jsonBody()
2725+ >>> firefox = webservice.get("/package-sets/hoary/firefox").jsonBody()
2726 >>> response = webservice.named_get(
2727- ... '/package-sets/thunderbird', 'getSourcesNotSharedBy', {},
2728+ ... '/package-sets/hoary/thunderbird', 'getSourcesNotSharedBy', {},
2729 ... other_package_set=firefox['self_link'])
2730 >>> print response
2731 HTTP/1.1 200 Ok
2732@@ -461,7 +528,7 @@
2733 >>> response = webservice.named_post(
2734 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
2735 ... person=name12['self_link'],
2736- ... packageset='firefox')
2737+ ... packageset=firefox['self_link'])
2738 >>> print response
2739 HTTP/1.1 201 Created
2740 ...
2741@@ -489,7 +556,7 @@
2742 >>> response = webservice.named_post(
2743 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
2744 ... person=name12['self_link'],
2745- ... packageset='mozilla')
2746+ ... packageset=mozilla['self_link'])
2747 >>> print response
2748 HTTP/1.1 201 Created
2749 ...
2750@@ -499,7 +566,7 @@
2751
2752 >>> response = webservice.named_get(
2753 ... ubuntu['main_archive_link'], 'getUploadersForPackageset', {},
2754- ... packageset='firefox')
2755+ ... packageset=firefox['self_link'])
2756 >>> print_payload(response)
2757 http://.../+archive/primary/+upload/name12?type=packageset&item=firefox
2758
2759@@ -508,7 +575,7 @@
2760
2761 >>> response = webservice.named_get(
2762 ... ubuntu['main_archive_link'], 'getUploadersForPackageset', {},
2763- ... packageset='firefox', direct_permissions=False)
2764+ ... packageset=firefox['self_link'], direct_permissions=False)
2765 >>> print_payload(response)
2766 http://.../+archive/primary/+upload/name12?type=packageset&item=firefox
2767 http://.../+archive/primary/+upload/name12?type=packageset&item=mozilla
2768@@ -518,7 +585,7 @@
2769 >>> response = webservice.named_post(
2770 ... ubuntu['main_archive_link'], 'deletePackagesetUploader', {},
2771 ... person=name12['self_link'],
2772- ... packageset='mozilla')
2773+ ... packageset=mozilla['self_link'])
2774 >>> print response
2775 HTTP/1.1 200 Ok
2776 ...
2777@@ -528,7 +595,7 @@
2778
2779 >>> response = webservice.named_get(
2780 ... ubuntu['main_archive_link'], 'getUploadersForPackageset', {},
2781- ... packageset='firefox', direct_permissions=False)
2782+ ... packageset=firefox['self_link'], direct_permissions=False)
2783 >>> print_payload(response)
2784 http://.../+archive/primary/+upload/name12?type=packageset&item=firefox
2785
2786@@ -538,7 +605,7 @@
2787 >>> response = webservice.named_post(
2788 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
2789 ... person=cprov['self_link'],
2790- ... packageset='mozilla')
2791+ ... packageset=mozilla['self_link'])
2792 >>> print response
2793 HTTP/1.1 201 Created
2794 ...
2795@@ -546,7 +613,7 @@
2796 >>> response = webservice.named_post(
2797 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
2798 ... person=cprov['self_link'],
2799- ... packageset='thunderbird')
2800+ ... packageset=thunderbird['self_link'])
2801 >>> print response
2802 HTTP/1.1 201 Created
2803 ...
2804@@ -599,6 +666,39 @@
2805 ...
2806 true
2807
2808+Archive permissions have distro series scope. We did not specify a distro
2809+series in the query above. Hence the `currentseries` in Ubuntu is assumed
2810+('hoary').
2811+The following query (note the additional 'distroseries' parameter) is
2812+thus equivalent:
2813+
2814+ >>> print ubuntu['current_series_link']
2815+ http://api.launchpad.dev/beta/ubuntu/hoary
2816+ >>> hoary = webservice.get("/ubuntu/hoary").jsonBody()
2817+ >>> print hoary['self_link']
2818+ http://api.launchpad.dev/beta/ubuntu/hoary
2819+
2820+ >>> response = webservice.named_get(
2821+ ... ubuntu['main_archive_link'], 'isSourceUploadAllowed',
2822+ ... {}, sourcepackagename='mozilla-firefox',
2823+ ... person=cprov['self_link'], distroseries=hoary['self_link'])
2824+ >>> print(response)
2825+ HTTP/1.1 200 Ok
2826+ ...
2827+ true
2828+
2829+Since cprov's upload permission is limited to the current distro series
2830+('hoary') checking the same permission for 'grumpy' will fail.
2831+
2832+ >>> response = webservice.named_get(
2833+ ... ubuntu['main_archive_link'], 'isSourceUploadAllowed',
2834+ ... {}, sourcepackagename='mozilla-firefox',
2835+ ... person=cprov['self_link'], distroseries=grumpy['self_link'])
2836+ >>> print(response)
2837+ HTTP/1.1 200 Ok
2838+ ...
2839+ false
2840+
2841 'name12' should not be allowed to upload the 'thunderbird' source package.
2842
2843 >>> response = webservice.named_get(
2844@@ -610,6 +710,80 @@
2845 ...
2846 false
2847
2848+Let's create a (related) package set in 'grumpy' and authorize 'name12' to
2849+upload to it.
2850+
2851+This will fail since 'name12' has no permissions applying to 'grumpy' yet.
2852+
2853+ >>> response = webservice.named_get(
2854+ ... ubuntu['main_archive_link'], 'isSourceUploadAllowed',
2855+ ... {}, sourcepackagename='thunderbird',
2856+ ... person=name12['self_link'], distroseries=grumpy['self_link'])
2857+ >>> print(response)
2858+ HTTP/1.1 200 Ok
2859+ ...
2860+ false
2861+
2862+Create a new package set ('grouchy-thunderbird') in 'grumpy'.
2863+
2864+ >>> response = webservice.named_post(
2865+ ... '/package-sets', 'new', {},
2866+ ... name=u'grouchy-thunderbird',
2867+ ... description=u'Contains all thunderbird packages in grumpy',
2868+ ... owner=name12['self_link'], distroseries=grumpy['self_link'],
2869+ ... related_set=thunderbird['self_link'])
2870+ >>> print response
2871+ HTTP/1.1 201 Created
2872+ ...
2873+
2874+ >>> response = webservice.named_get(
2875+ ... thunderbird['self_link'], 'relatedSets', {})
2876+ >>> print_payload(response)
2877+ http://api.launchpad.dev/beta/package-sets/grumpy/grouchy-thunderbird
2878+
2879+Associate 'grouchy-thunderbird' with the appropriate source packages.
2880+
2881+ >>> response = webservice.named_post(
2882+ ... '/package-sets/grumpy/grouchy-thunderbird', 'addSources', {},
2883+ ... names=['thunderbird', 'language-pack-de'])
2884+ >>> print response
2885+ HTTP/1.1 200 Ok
2886+ ...
2887+
2888+Grant 'name12' upload permissions to 'grouchy-thunderbird' in 'grumpy'.
2889+
2890+ >>> grouchy_bird = webservice.get(
2891+ ... "/package-sets/grumpy/grouchy-thunderbird").jsonBody()
2892+
2893+ >>> response = webservice.named_post(
2894+ ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
2895+ ... person=name12['self_link'],
2896+ ... packageset=grouchy_bird['self_link'])
2897+ >>> print response
2898+ HTTP/1.1 201 Created
2899+ ...
2900+
2901+Does the new archive permission show up?
2902+
2903+ >>> response = webservice.named_get(
2904+ ... ubuntu['main_archive_link'], 'getPackagesetsForUploader', {},
2905+ ... person=name12['self_link'])
2906+ >>> print_payload(response)
2907+ http://...+archive/primary/+upload/name12?type=packageset&item=firefox
2908+ http://...+archive/primary/+upload/name12?type=packageset&item=grouchy-thunderbird
2909+
2910+And now 'name12' should be authorized to upload source package
2911+'thunderbird' in 'grumpy'.
2912+
2913+ >>> response = webservice.named_get(
2914+ ... ubuntu['main_archive_link'], 'isSourceUploadAllowed',
2915+ ... {}, sourcepackagename='thunderbird',
2916+ ... person=name12['self_link'], distroseries=grumpy['self_link'])
2917+ >>> print(response)
2918+ HTTP/1.1 200 Ok
2919+ ...
2920+ true
2921+
2922 Sometimes it's also interesting to see what package set based upload
2923 permissions apply to a source package irrespective of the principal.
2924
2925
2926=== modified file 'lib/lp/soyuz/templates/person-archive-subscriptions.pt'
2927--- lib/lp/soyuz/templates/person-archive-subscriptions.pt 2009-09-18 07:46:03 +0000
2928+++ lib/lp/soyuz/templates/person-archive-subscriptions.pt 2009-11-03 18:13:39 +0000
2929@@ -34,7 +34,7 @@
2930 </td>
2931 <td>
2932 <tal:active condition="token">
2933- <a tal:attributes="href subscription/fmt:url" class="info">
2934+ <a tal:attributes="href subscription/fmt:url" class="sprite info">
2935 View
2936 </a>
2937 </tal:active>
2938
2939=== added file 'lib/lp/soyuz/tests/test_packageset.py'
2940--- lib/lp/soyuz/tests/test_packageset.py 1970-01-01 00:00:00 +0000
2941+++ lib/lp/soyuz/tests/test_packageset.py 2009-11-03 18:13:43 +0000
2942@@ -0,0 +1,187 @@
2943+# Copyright 2009 Canonical Ltd. This software is licensed under the
2944+# GNU Affero General Public License version 3 (see the file LICENSE).
2945+
2946+"""Test Packageset features."""
2947+
2948+from zope.component import getUtility
2949+
2950+from canonical.testing import LaunchpadZopelessLayer
2951+
2952+from lp.testing import TestCaseWithFactory
2953+from lp.registry.interfaces.distribution import IDistributionSet
2954+from lp.registry.interfaces.distroseries import DistroSeriesStatus
2955+from lp.soyuz.interfaces.packageset import (
2956+ DuplicatePackagesetName, IPackagesetSet)
2957+
2958+
2959+class TestPackagesetSet(TestCaseWithFactory):
2960+
2961+ layer = LaunchpadZopelessLayer
2962+
2963+ def setUp(self):
2964+ """Setup a distribution with multiple distroseries."""
2965+ super(TestPackagesetSet, self).setUp()
2966+ self.distribution = getUtility(IDistributionSet).getByName(
2967+ 'ubuntu')
2968+ self.distroseries_current = self.distribution.currentseries
2969+ self.distroseries_experimental = self.factory.makeDistroRelease(
2970+ distribution = self.distribution, name="experimental",
2971+ status=DistroSeriesStatus.EXPERIMENTAL)
2972+
2973+ self.person1 = self.factory.makePerson(
2974+ name='hacker', displayname=u'Happy Hacker')
2975+
2976+ self.packageset_set = getUtility(IPackagesetSet)
2977+
2978+ def test_new_defaults_to_current_distroseries(self):
2979+ # If the distroseries is not provided, the current development
2980+ # distroseries will be assumed.
2981+ packageset = self.packageset_set.new(
2982+ u'kernel', u'Contains all OS kernel packages', self.person1)
2983+
2984+ self.failUnlessEqual(
2985+ self.distroseries_current, packageset.distroseries)
2986+
2987+ def test_new_with_specified_distroseries(self):
2988+ # A distroseries can be provided when creating a package set.
2989+ packageset = self.packageset_set.new(
2990+ u'kernel', u'Contains all OS kernel packages', self.person1,
2991+ distroseries=self.distroseries_experimental)
2992+
2993+ self.failUnlessEqual(
2994+ self.distroseries_experimental, packageset.distroseries)
2995+
2996+ def test_new_creates_new_packageset_group(self):
2997+ # Creating a new packageset should also create a new packageset
2998+ # group with the same owner.
2999+ packageset = self.packageset_set.new(
3000+ u'kernel', u'Contains all OS kernel packages', self.person1,
3001+ distroseries=self.distroseries_experimental)
3002+
3003+ self.failUnlessEqual(
3004+ self.person1, packageset.packagesetgroup.owner)
3005+
3006+ def test_new_duplicate_name_for_same_distroseries(self):
3007+ # Creating a packageset with a duplicate name for the
3008+ # given distroseries will fail.
3009+ packageset = self.packageset_set.new(
3010+ u'kernel', u'Contains all OS kernel packages', self.person1,
3011+ distroseries=self.distroseries_experimental)
3012+
3013+ self.failUnlessRaises(
3014+ DuplicatePackagesetName, self.packageset_set.new,
3015+ u'kernel', u'A packageset with a duplicate name', self.person1,
3016+ distroseries=self.distroseries_experimental)
3017+
3018+ def test_new_duplicate_name_for_different_distroseries(self):
3019+ # Creating a packageset with a duplicate name but for a different
3020+ # series is no problem.
3021+ packageset = self.packageset_set.new(
3022+ u'kernel', u'Contains all OS kernel packages', self.person1)
3023+
3024+ packageset2 = self.packageset_set.new(
3025+ u'kernel', u'A packageset with a duplicate name', self.person1,
3026+ distroseries=self.distroseries_experimental)
3027+ self.assertEqual(packageset.name, packageset2.name)
3028+
3029+ def test_new_related_packageset(self):
3030+ # Creating a new package set while specifying a `related_set` should
3031+ # have the effect that the former ends up in the same group as the
3032+ # latter.
3033+ pset1 = self.packageset_set.new(
3034+ u'kernel', u'Contains all OS kernel packages', self.person1)
3035+
3036+ pset2 = self.packageset_set.new(
3037+ u'kernel', u'A related package set.', self.person1,
3038+ distroseries=self.distroseries_experimental, related_set=pset1)
3039+ self.assertEqual(pset1.packagesetgroup, pset2.packagesetgroup)
3040+
3041+ def test_get_by_name_in_current_distroseries(self):
3042+ # IPackagesetSet.getByName() will return the package set in the
3043+ # current distroseries if the optional `distroseries` parameter is
3044+ # omitted.
3045+ pset1 = self.packageset_set.new(
3046+ u'kernel', u'Contains all OS kernel packages', self.person1)
3047+ pset2 = self.packageset_set.new(
3048+ u'kernel', u'A related package set.', self.person1,
3049+ distroseries=self.distroseries_experimental, related_set=pset1)
3050+ pset_found = getUtility(IPackagesetSet).getByName('kernel')
3051+ self.assertEqual(pset1, pset_found)
3052+
3053+ def test_get_by_name_in_specified_distroseries(self):
3054+ # IPackagesetSet.getByName() will return the package set in the
3055+ # specified distroseries.
3056+ pset1 = self.packageset_set.new(
3057+ u'kernel', u'Contains all OS kernel packages', self.person1)
3058+ pset2 = self.packageset_set.new(
3059+ u'kernel', u'A related package set.', self.person1,
3060+ distroseries=self.distroseries_experimental, related_set=pset1)
3061+ pset_found = getUtility(IPackagesetSet).getByName(
3062+ 'kernel', distroseries=self.distroseries_experimental)
3063+ self.assertEqual(pset2, pset_found)
3064+
3065+
3066+class TestPackageset(TestCaseWithFactory):
3067+
3068+ layer = LaunchpadZopelessLayer
3069+
3070+ def setUp(self):
3071+ """Setup a distribution with multiple distroseries."""
3072+ super(TestPackageset, self).setUp()
3073+ self.distribution = getUtility(IDistributionSet).getByName(
3074+ 'ubuntu')
3075+ self.distroseries_current = self.distribution.currentseries
3076+ self.distroseries_experimental = self.factory.makeDistroRelease(
3077+ distribution = self.distribution, name="experimental",
3078+ status=DistroSeriesStatus.EXPERIMENTAL)
3079+ self.distroseries_experimental2 = self.factory.makeDistroRelease(
3080+ distribution = self.distribution, name="experimental2",
3081+ status=DistroSeriesStatus.EXPERIMENTAL)
3082+
3083+ self.person1 = self.factory.makePerson(
3084+ name='hacker', displayname=u'Happy Hacker')
3085+
3086+ self.packageset_set = getUtility(IPackagesetSet)
3087+
3088+ def test_no_related_sets(self):
3089+ # If the package set is the only one in the group the result set
3090+ # returned by relatedSets() is empty.
3091+ packageset = self.packageset_set.new(
3092+ u'kernel', u'Contains all OS kernel packages', self.person1)
3093+
3094+ self.failUnlessEqual(packageset.relatedSets().count(), 0)
3095+
3096+ def test_related_set_found(self):
3097+ # Creating a new package set while specifying a `related_set` should
3098+ # have the effect that the former ends up in the same group as the
3099+ # latter.
3100+
3101+ # The original package set.
3102+ pset1 = self.packageset_set.new(
3103+ u'kernel', u'Contains all OS kernel packages', self.person1)
3104+
3105+ # A related package set.
3106+ pset2 = self.packageset_set.new(
3107+ u'kernel', u'A related package set.', self.person1,
3108+ distroseries=self.distroseries_experimental, related_set=pset1)
3109+ self.assertEqual(pset1.packagesetgroup, pset2.packagesetgroup)
3110+
3111+ # An unrelated package set with the same name.
3112+ pset3 = self.packageset_set.new(
3113+ u'kernel', u'Unrelated package set.', self.person1,
3114+ distroseries=self.distroseries_experimental2)
3115+ self.assertNotEqual(pset2.packagesetgroup, pset3.packagesetgroup)
3116+
3117+ # Make sure 'pset2' is related to 'pset1'.
3118+ related = pset1.relatedSets()
3119+ self.assertEqual(related.count(), 1)
3120+ self.assertEqual(related[0], pset2)
3121+
3122+ # And the other way around ..
3123+ related = pset2.relatedSets()
3124+ self.assertEqual(related.count(), 1)
3125+ self.assertEqual(related[0], pset1)
3126+
3127+ # Unsurprisingly, the unrelated package set is not associated with any
3128+ # other package set.
3129+ self.failUnlessEqual(pset3.relatedSets().count(), 0)
3130
3131=== modified file 'scripts/ftpmaster-tools/_syncorigins.py'
3132--- scripts/ftpmaster-tools/_syncorigins.py 2009-07-23 02:33:14 +0000
3133+++ scripts/ftpmaster-tools/_syncorigins.py 2009-11-03 18:13:42 +0000
3134@@ -12,7 +12,7 @@
3135 "debian": {
3136 "name": "Debian",
3137 "url": "http://ftp.debian.org/debian/",
3138- "default suite": "unstable",
3139+ "default suite": "testing",
3140 "default component": "main",
3141 "dsc": "must be signed and valid"
3142 },
3143
3144=== modified file 'utilities/pgmassacre.py'
3145--- utilities/pgmassacre.py 2009-06-24 20:15:50 +0000
3146+++ utilities/pgmassacre.py 2009-11-03 18:13:41 +0000
3147@@ -11,6 +11,7 @@
3148
3149 # Nothing but system installed libraries - this script sometimes
3150 # gets installed standalone with no Launchpad tree available.
3151+from distutils.version import LooseVersion
3152 import sys
3153 import time
3154 import psycopg2
3155@@ -184,12 +185,20 @@
3156 error_msg = None
3157 con = connect()
3158 con.set_isolation_level(0) # Autocommit required for CREATE DATABASE.
3159+ create_db_cmd = """
3160+ CREATE DATABASE %s WITH ENCODING='UTF8' TEMPLATE=%s
3161+ """ % (database, template)
3162+ # 8.4 allows us to create empty databases with a different locale
3163+ # to template1 by using the template0 database as a template.
3164+ # We make use of this feature so we don't have to care what locale
3165+ # was used to create the database cluster rather than requiring it
3166+ # to be rebuilt in the C locale.
3167+ if pg_version >= LooseVersion("8.4.0") and template == "template0":
3168+ create_db_cmd += "LC_COLLATE='C' LC_CTYPE='C'"
3169 while now < start + 20:
3170 cur = con.cursor()
3171 try:
3172- cur.execute(
3173- "CREATE DATABASE %s WITH ENCODING='UTF8' TEMPLATE=%s"
3174- % (database, template))
3175+ cur.execute(create_db_cmd)
3176 con.close()
3177 return 0
3178 except psycopg2.Error, exception:
3179@@ -219,6 +228,7 @@
3180
3181
3182 options = None
3183+pg_version = None # LooseVersion - Initialized in main()
3184
3185
3186 def main():
3187@@ -243,6 +253,12 @@
3188
3189 con = connect()
3190 cur = con.cursor()
3191+
3192+ # Store the database version for version specific code.
3193+ global pg_version
3194+ cur.execute("show server_version")
3195+ pg_version = LooseVersion(cur.fetchone()[0])
3196+
3197 # Ensure the template database exists.
3198 if options.template is not None:
3199 cur.execute(