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
=== modified file 'database/sampledata/current-dev.sql'
--- database/sampledata/current-dev.sql 2009-09-07 01:46:23 +0000
+++ database/sampledata/current-dev.sql 2009-11-03 18:13:40 +0000
@@ -969,18 +969,18 @@
969969
970ALTER TABLE archive DISABLE TRIGGER ALL;970ALTER TABLE archive DISABLE TRIGGER ALL;
971971
972INSERT 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);972INSERT 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);
973INSERT 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);973INSERT 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);
974INSERT 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);974INSERT 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);
975INSERT 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);975INSERT 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);
976INSERT 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);976INSERT 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);
977INSERT 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);977INSERT 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);
978INSERT 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);978INSERT 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);
979INSERT 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);979INSERT 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);
980INSERT 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);980INSERT 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);
981INSERT 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);981INSERT 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);
982INSERT 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);982INSERT 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);
983INSERT 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);983INSERT 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);
984984
985985
986ALTER TABLE archive ENABLE TRIGGER ALL;986ALTER TABLE archive ENABLE TRIGGER ALL;
987987
=== modified file 'database/schema/Makefile'
--- database/schema/Makefile 2009-08-04 22:16:48 +0000
+++ database/schema/Makefile 2009-11-03 18:13:41 +0000
@@ -113,7 +113,7 @@
113 @ echo "* eg. sudo -u postgres make create"113 @ echo "* eg. sudo -u postgres make create"
114 @ echo114 @ echo
115 @ echo "* Creating database \"$(EMPTY_DBNAME)\"."115 @ echo "* Creating database \"$(EMPTY_DBNAME)\"."
116 @ ${CREATEDB} template1 ${EMPTY_DBNAME}116 @ ${CREATEDB} template0 ${EMPTY_DBNAME}
117 @ if ! `createlang -l ${EMPTY_DBNAME} | grep -qs plpythonu`; then \117 @ if ! `createlang -l ${EMPTY_DBNAME} | grep -qs plpythonu`; then \
118 echo "* Installing PL/PythonU"; \118 echo "* Installing PL/PythonU"; \
119 createlang -d ${EMPTY_DBNAME} plpythonu; \119 createlang -d ${EMPTY_DBNAME} plpythonu; \
@@ -142,12 +142,12 @@
142142
143 @ echo "* Creating session databases '${SESSION_DBNAME}' (if necessary)"143 @ echo "* Creating session databases '${SESSION_DBNAME}' (if necessary)"
144 @if [ "$$((`psql -l | grep -w ${SESSION_DBNAME} | wc -l`))" = '0' ]; \144 @if [ "$$((`psql -l | grep -w ${SESSION_DBNAME} | wc -l`))" = '0' ]; \
145 then ${CREATEDB} template1 ${SESSION_DBNAME} ; \145 then ${CREATEDB} template0 ${SESSION_DBNAME} ; \
146 createlang plpgsql ${SESSION_DBNAME}; \146 createlang plpgsql ${SESSION_DBNAME}; \
147 psql -q -d ${SESSION_DBNAME} -f launchpad_session.sql ; \147 psql -q -d ${SESSION_DBNAME} -f launchpad_session.sql ; \
148 fi148 fi
149 @ echo "* Creating session database '${TEST_SESSION_DBNAME}'"149 @ echo "* Creating session database '${TEST_SESSION_DBNAME}'"
150 @ ${CREATEDB} template1 ${TEST_SESSION_DBNAME}150 @ ${CREATEDB} template0 ${TEST_SESSION_DBNAME}
151 @ createlang plpgsql ${TEST_SESSION_DBNAME}151 @ createlang plpgsql ${TEST_SESSION_DBNAME}
152 @ psql -q -d ${TEST_SESSION_DBNAME} -f launchpad_session.sql152 @ psql -q -d ${TEST_SESSION_DBNAME} -f launchpad_session.sql
153153
154154
=== modified file 'database/schema/comments.sql'
--- database/schema/comments.sql 2009-08-30 01:41:38 +0000
+++ database/schema/comments.sql 2009-11-03 18:13:41 +0000
@@ -763,8 +763,11 @@
763763
764-- SprintAttendance764-- SprintAttendance
765COMMENT ON TABLE SprintAttendance IS 'The record that someone will be attending a particular sprint or meeting.';765COMMENT ON TABLE SprintAttendance IS 'The record that someone will be attending a particular sprint or meeting.';
766COMMENT ON COLUMN SprintAttendance.attendee IS 'The person attending the sprint.';
767COMMENT ON COLUMN SprintAttendance.sprint IS 'The sprint the person is attending.';
766COMMENT ON COLUMN SprintAttendance.time_starts IS 'The time from which the person will be available to participate in meetings at the sprint.';768COMMENT ON COLUMN SprintAttendance.time_starts IS 'The time from which the person will be available to participate in meetings at the sprint.';
767COMMENT 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.';769COMMENT 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.';
770COMMENT ON COLUMN SprintAttendance.is_physical IS 'Is the person physically attending the sprint';
768771
769772
770-- SprintSpecification773-- SprintSpecification
@@ -1862,6 +1865,7 @@
1862COMMENT 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.';1865COMMENT 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.';
1863COMMENT ON COLUMN Archive.num_old_versions_published IS 'The number of versions of a package to keep published before older versions are superseded.';1866COMMENT ON COLUMN Archive.num_old_versions_published IS 'The number of versions of a package to keep published before older versions are superseded.';
1864COMMENT ON COLUMN Archive.relative_build_score IS 'A delta to the build score that is applied to all builds in this archive.';1867COMMENT ON COLUMN Archive.relative_build_score IS 'A delta to the build score that is applied to all builds in this archive.';
1868COMMENT 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.';
18651869
1866-- ArchiveAuthToken1870-- ArchiveAuthToken
18671871
@@ -2235,11 +2239,20 @@
22352239
2236-- Packageset2240-- Packageset
22372241
2238COMMENT ON TABLE Packageset IS 'Package sets facilitate the grouping of packages for purposes like the control of upload permissions, et.';2242COMMENT 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.';
2239COMMENT ON COLUMN Packageset.date_created IS 'Date and time of creation.';2243COMMENT ON COLUMN Packageset.date_created IS 'Date and time of creation.';
2240COMMENT ON COLUMN Packageset.owner IS 'The Person or team who owns the package set';2244COMMENT ON COLUMN Packageset.owner IS 'The Person or team who owns the package set';
2241COMMENT ON COLUMN Packageset.name IS 'The name for the package set on hand.';2245COMMENT ON COLUMN Packageset.name IS 'The name for the package set on hand.';
2242COMMENT ON COLUMN Packageset.description IS 'The description for the package set on hand.';2246COMMENT ON COLUMN Packageset.description IS 'The description for the package set on hand.';
2247COMMENT ON COLUMN Packageset.packagesetgroup IS 'The group this package set is affiliated with.';
2248COMMENT ON COLUMN Packageset.distroseries IS 'The distro series this package set belongs to.';
2249
2250-- PackagesetGroup
2251
2252COMMENT ON TABLE PackagesetGroup IS 'Package set groups keep track of equivalent package sets across distro series boundaries.';
2253COMMENT ON COLUMN Packageset.date_created IS 'Date and time of creation.';
2254COMMENT ON COLUMN Packageset.owner IS 'The Person or team who owns the package
2255set group.';
22432256
2244-- PackagesetSources2257-- PackagesetSources
22452258
@@ -2256,3 +2269,9 @@
2256COMMENT 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.';2269COMMENT 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.';
2257COMMENT ON COLUMN FlatPackagesetInclusion.parent IS 'The package set that is (directly or indirectly) including a subset.';2270COMMENT ON COLUMN FlatPackagesetInclusion.parent IS 'The package set that is (directly or indirectly) including a subset.';
2258COMMENT ON COLUMN FlatPackagesetInclusion.child IS 'The package set that is being included as a subset.';2271COMMENT ON COLUMN FlatPackagesetInclusion.child IS 'The package set that is being included as a subset.';
2272
2273-- SourcePackageFormatSelection
2274COMMENT ON TABLE SourcePackageFormatSelection IS 'Allowed source package formats for a given distroseries.';
2275COMMENT ON COLUMN SourcePackageFormatSelection.distroseries IS 'Refers to the distroseries in question.';
2276COMMENT ON COLUMN SourcePackageFormatSelection.format IS 'The SourcePackageFormat to allow.';
2277
22592278
=== modified file 'database/schema/fti.py'
--- database/schema/fti.py 2009-06-24 21:17:33 +0000
+++ database/schema/fti.py 2009-11-03 18:13:42 +0000
@@ -13,6 +13,7 @@
1313
14import _pythonpath14import _pythonpath
1515
16from distutils.version import LooseVersion
16import sys17import sys
17import os.path18import os.path
18from optparse import OptionParser19from optparse import OptionParser
@@ -287,9 +288,9 @@
287 """Setup and install tsearch2 if isn't already"""288 """Setup and install tsearch2 if isn't already"""
288289
289 # tsearch2 is out-of-the-box in 8.3+290 # tsearch2 is out-of-the-box in 8.3+
290 v83 = get_pgversion(con).startswith('8.3')291 required = LooseVersion('8.3.0')
291292 assert get_pgversion(con) >= required, (
292 assert v83, 'This script only supports PostgreSQL 8.3'293 'This script only supports PostgreSQL 8.3+')
293294
294 schema_exists = bool(execute(295 schema_exists = bool(execute(
295 con, "SELECT COUNT(*) FROM pg_namespace WHERE nspname='ts2'",296 con, "SELECT COUNT(*) FROM pg_namespace WHERE nspname='ts2'",
@@ -608,18 +609,13 @@
608609
609def get_pgversion(con):610def get_pgversion(con):
610 rows = execute(con, r"show server_version", results=True)611 rows = execute(con, r"show server_version", results=True)
611 return rows[0][0]612 return LooseVersion(rows[0][0])
612613
613614
614def get_tsearch2_sql_path(con):615def get_tsearch2_sql_path(con):
615 pgversion = get_pgversion(con)616 major, minor = get_pgversion(con).version[:2]
616 if pgversion.startswith('8.2.'):617 path = os.path.join(
617 path = os.path.join(PGSQL_BASE, '8.2', 'contrib', 'tsearch2.sql')618 PGSQL_BASE, '%d.%d' % (major, minor), 'contrib', 'tsearch2.sql')
618 elif pgversion.startswith('8.3.'):
619 path = os.path.join(PGSQL_BASE, '8.3', 'contrib', 'tsearch2.sql')
620 else:
621 raise RuntimeError('Unknown version %s' % pgversion)
622
623 assert os.path.exists(path), '%s does not exist' % path619 assert os.path.exists(path), '%s does not exist' % path
624 return path620 return path
625621
626622
=== modified file 'database/schema/patch-2207-00-3.sql'
--- database/schema/patch-2207-00-3.sql 2009-09-18 04:13:18 +0000
+++ database/schema/patch-2207-00-3.sql 2009-11-03 18:13:41 +0000
@@ -1,7 +1,12 @@
1SET client_min_messages TO ERROR;1SET client_min_messages TO ERROR;
22
3UPDATE BugActivity SET person=(SELECT id FROM Person WHERE name='janitor')3UPDATE BugActivity SET person=(SELECT id FROM Person WHERE name='janitor')
4WHERE person NOT IN (SELECT id FROM Person);4FROM (
5 SELECT BugActivity.id
6 FROM BugActivity LEFT OUTER JOIN Person ON BugActivity.person = Person.id
7 WHERE Person.id IS NULL
8 ) AS Whatever
9WHERE Whatever.id = BugActivity.id;
510
6ALTER TABLE BugActivity11ALTER TABLE BugActivity
7 ADD CONSTRAINT bugactivity__person__fk12 ADD CONSTRAINT bugactivity__person__fk
813
=== added file 'database/schema/patch-2207-04-0.sql'
--- database/schema/patch-2207-04-0.sql 1970-01-01 00:00:00 +0000
+++ database/schema/patch-2207-04-0.sql 2009-11-03 18:13:39 +0000
@@ -0,0 +1,13 @@
1-- Copyright 2009 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6
7-- Add a column to indicate that the attendee is physically attending
8-- the sprint.
9ALTER TABLE SprintAttendance
10 ADD COLUMN is_physical BOOLEAN NOT NULL DEFAULT FALSE;
11
12
13INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 04, 0);
014
=== added file 'database/schema/patch-2207-05-0.sql'
--- database/schema/patch-2207-05-0.sql 1970-01-01 00:00:00 +0000
+++ database/schema/patch-2207-05-0.sql 2009-11-03 18:13:40 +0000
@@ -0,0 +1,12 @@
1-- Copyright 2009 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6
7-- Add a column for external archive dependencies.
8ALTER TABLE Archive
9 ADD COLUMN external_dependencies text DEFAULT NULL;
10
11
12INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 05, 0);
013
=== added file 'database/schema/patch-2207-06-0.sql'
--- database/schema/patch-2207-06-0.sql 1970-01-01 00:00:00 +0000
+++ database/schema/patch-2207-06-0.sql 2009-11-03 18:13:43 +0000
@@ -0,0 +1,122 @@
1-- Copyright 2009 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6-- ** PART 1 ** Create the 'packagesetgroup' table and the
7-- 'packageset.packagesetgroup' foreign key,
8-- populate the 'packagesetgroup' table
9
10-- This table keeps track of package sets that are equivalent across
11-- distro series boundaries.
12CREATE SEQUENCE packagesetgroup_id_seq
13 START WITH 1
14 INCREMENT BY 1
15 NO MAXVALUE
16 NO MINVALUE
17 CACHE 1;
18CREATE TABLE packagesetgroup (
19 id integer NOT NULL DEFAULT nextval('packagesetgroup_id_seq'),
20 date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
21 owner integer NOT NULL,
22 -- Please note: the 'name' column is only here to ease the data migration
23 -- and will be dropped at the end of this patch.
24 name text NOT NULL
25);
26ALTER SEQUENCE packagesetgroup_id_seq OWNED BY packagesetgroup.id;
27ALTER TABLE ONLY packagesetgroup
28 ADD CONSTRAINT packagesetgroup_pkey PRIMARY KEY (id);
29ALTER TABLE ONLY packagesetgroup
30 ADD CONSTRAINT packagesetgroup__owner__fk
31 FOREIGN KEY (owner) REFERENCES person(id);
32
33-- Package sets and their clones belong to the same package set group.
34ALTER TABLE ONLY packageset ADD COLUMN packagesetgroup integer;
35ALTER TABLE ONLY packageset
36 ADD CONSTRAINT packageset__packagesetgroup__fk
37 FOREIGN KEY (packagesetgroup) REFERENCES packagesetgroup(id);
38
39-- Create a group for each of the original (karmic koala) package sets.
40INSERT INTO packagesetgroup(owner, name)
41SELECT packageset.owner, packageset.name
42FROM packageset WHERE NOT packageset.name LIKE('lucid-%');
43
44
45-- ** PART 2 ** Associate the karmic koala package sets and their lucid lynx
46-- clones with the appropriate package set groups
47
48-- Update the karmic koala package sets so they reference their groups.
49UPDATE packageset SET packagesetgroup = packagesetgroup.id
50FROM packagesetgroup WHERE packageset.name = packagesetgroup.name;
51
52-- Update the lucid lynx package set *clones* so they reference their groups
53-- as well.
54UPDATE packageset SET packagesetgroup = packagesetgroup.id
55FROM packagesetgroup WHERE packageset.name = 'lucid-' || packagesetgroup.name;
56
57-- ** PART 3 ** Add the 'packageset.distroseries' foreign key and
58-- initialise it for the existing package sets.
59
60-- A package set lives in a distro series context.
61ALTER TABLE ONLY packageset ADD COLUMN distroseries integer;
62
63-- Define the foreign key constraint.
64ALTER TABLE ONLY packageset
65 ADD CONSTRAINT packageset__distroseries__fk
66 FOREIGN KEY (distroseries) REFERENCES distroseries(id);
67
68-- First migrate the original package sets created for the karmic koala.
69UPDATE packageset SET distroseries = distroseries.id FROM distroseries
70WHERE distroseries.name = 'karmic' AND NOT packageset.name LIKE('lucid-%');
71
72-- Migrate the lucid lynx package sets next.
73UPDATE packageset SET distroseries = distroseries.id FROM distroseries
74WHERE distroseries.name = 'lucid' AND packageset.name LIKE('lucid-%');
75
76-- Make the 'distroseries' foreign key mandatory.
77ALTER TABLE ONLY packageset ALTER COLUMN distroseries SET NOT NULL;
78
79-- The package set name is now only unique in conjunction with a distro series.
80ALTER TABLE ONLY packageset
81 DROP CONSTRAINT packageset_name_key;
82ALTER TABLE ONLY packageset
83 ADD CONSTRAINT packageset__name__distroseries__key UNIQUE (name, distroseries);
84
85-- ** PART 4 ** Strip off the 'lucid-' prefix of the lucid lynx
86-- package set names
87UPDATE packageset SET name = substring(name FROM length('lucid-')+1)
88WHERE name LIKE('lucid-%');
89
90-- ** PART 5 ** Create package set groups for package sets that were added in
91-- lucid lynx but do not exist in the karmic koala,
92-- associate these package sets with their newly created groups
93INSERT INTO packagesetgroup(owner, name)
94SELECT packageset.owner, packageset.name
95FROM packageset, distroseries WHERE
96 packageset.packagesetgroup IS NULL
97 AND packageset.distroseries = distroseries.id
98 AND distroseries.name = 'lucid';
99
100UPDATE packageset SET packagesetgroup = packagesetgroup.id
101FROM packagesetgroup, distroseries
102WHERE
103 packageset.packagesetgroup IS NULL
104 AND packageset.distroseries = distroseries.id
105 AND distroseries.name = 'lucid'
106 AND packageset.name = packagesetgroup.name;
107
108-- ** PART 6 ** Make the 'packageset.packagesetgroup' foreign key mandatory
109ALTER TABLE ONLY packageset ALTER COLUMN packagesetgroup SET NOT NULL;
110
111-- ** PART 7 ** Drop the 'packagesetgroup.name' column that was only added
112-- for data migration purposes.
113ALTER TABLE ONLY packagesetgroup DROP COLUMN name;
114
115-- Define indices on the newly added foreign keys.
116CREATE INDEX packageset__packagesetgroup__idx
117 ON packageset(packagesetgroup);
118CREATE INDEX packageset__distroseries__idx
119 ON packageset(distroseries);
120CREATE INDEX packagesetgroup__owner__idx ON PackageSetGroup(owner);
121
122INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 06, 0);
0123
=== added file 'database/schema/patch-2207-08-0.sql'
--- database/schema/patch-2207-08-0.sql 1970-01-01 00:00:00 +0000
+++ database/schema/patch-2207-08-0.sql 2009-11-03 18:13:40 +0000
@@ -0,0 +1,20 @@
1-- Copyright 2009 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6CREATE TABLE sourcepackageformatselection (
7 id serial PRIMARY KEY,
8 distroseries integer NOT NULL
9 CONSTRAINT sourceformatselection__distroseries__fk
10 REFERENCES distroseries,
11 format integer NOT NULL,
12 CONSTRAINT sourceformatselection__distroseries__format__key
13 UNIQUE (distroseries, format)
14);
15
16-- Allow all series to accept format 1.0 by default.
17INSERT INTO sourcepackageformatselection (distroseries, format)
18 SELECT id, 0 AS format FROM distroseries;
19
20INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 08, 0);
021
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2009-10-31 01:41:29 +0000
+++ database/schema/security.cfg 2009-11-03 18:13:41 +0000
@@ -227,6 +227,7 @@
227public.packagediff = SELECT, INSERT, UPDATE, DELETE227public.packagediff = SELECT, INSERT, UPDATE, DELETE
228public.packagediff = SELECT, INSERT, UPDATE, DELETE228public.packagediff = SELECT, INSERT, UPDATE, DELETE
229public.packageset = SELECT, INSERT, UPDATE, DELETE229public.packageset = SELECT, INSERT, UPDATE, DELETE
230public.packagesetgroup = SELECT, INSERT, UPDATE, DELETE
230public.packagesetsources = SELECT, INSERT, UPDATE, DELETE231public.packagesetsources = SELECT, INSERT, UPDATE, DELETE
231public.packagesetinclusion = SELECT, INSERT, UPDATE, DELETE232public.packagesetinclusion = SELECT, INSERT, UPDATE, DELETE
232public.flatpackagesetinclusion = SELECT, INSERT, UPDATE, DELETE233public.flatpackagesetinclusion = SELECT, INSERT, UPDATE, DELETE
@@ -649,6 +650,7 @@
649public.messagechunk = SELECT, INSERT650public.messagechunk = SELECT, INSERT
650# Merge notifications651# Merge notifications
651public.codereviewvote = SELECT652public.codereviewvote = SELECT
653public.codereviewmessage = SELECT
652654
653[branch-distro]655[branch-distro]
654type=user656type=user
@@ -785,6 +787,7 @@
785public.packagecopyrequest = SELECT, INSERT, UPDATE787public.packagecopyrequest = SELECT, INSERT, UPDATE
786public.packagediff = SELECT, INSERT, UPDATE788public.packagediff = SELECT, INSERT, UPDATE
787public.packageset = SELECT789public.packageset = SELECT
790public.packagesetgroup = SELECT
788public.packagesetsources = SELECT, INSERT, UPDATE, DELETE791public.packagesetsources = SELECT, INSERT, UPDATE, DELETE
789public.packagesetinclusion = SELECT, INSERT, UPDATE, DELETE792public.packagesetinclusion = SELECT, INSERT, UPDATE, DELETE
790public.flatpackagesetinclusion = SELECT, INSERT, UPDATE, DELETE793public.flatpackagesetinclusion = SELECT, INSERT, UPDATE, DELETE
@@ -862,6 +865,7 @@
862public.teammembership = SELECT865public.teammembership = SELECT
863public.gpgkey = SELECT866public.gpgkey = SELECT
864public.packageset = SELECT867public.packageset = SELECT
868public.packagesetgroup = SELECT
865public.packagesetsources = SELECT869public.packagesetsources = SELECT
866public.packagesetinclusion = SELECT870public.packagesetinclusion = SELECT
867public.flatpackagesetinclusion = SELECT871public.flatpackagesetinclusion = SELECT
@@ -917,7 +921,7 @@
917public.bugpackageinfestation = SELECT, INSERT, UPDATE921public.bugpackageinfestation = SELECT, INSERT, UPDATE
918public.bugproductinfestation = SELECT, INSERT, UPDATE922public.bugproductinfestation = SELECT, INSERT, UPDATE
919public.bugsubscription = SELECT, INSERT, UPDATE, DELETE923public.bugsubscription = SELECT, INSERT, UPDATE, DELETE
920public.bugtask = SELECT, INSERT, UPDATE924public.bugtask = SELECT, INSERT, UPDATE, DELETE
921public.bugtracker = SELECT, INSERT, UPDATE, DELETE925public.bugtracker = SELECT, INSERT, UPDATE, DELETE
922public.bugtrackeralias = SELECT, INSERT, UPDATE, DELETE926public.bugtrackeralias = SELECT, INSERT, UPDATE, DELETE
923public.bugwatch = SELECT, INSERT, UPDATE, DELETE927public.bugwatch = SELECT, INSERT, UPDATE, DELETE
@@ -1062,6 +1066,7 @@
1062public.archive = SELECT, INSERT, UPDATE1066public.archive = SELECT, INSERT, UPDATE
1063public.archivearch = SELECT, INSERT, UPDATE1067public.archivearch = SELECT, INSERT, UPDATE
1064public.packageset = SELECT1068public.packageset = SELECT
1069public.packagesetgroup = SELECT
1065public.packagesetsources = SELECT1070public.packagesetsources = SELECT
1066public.packagesetinclusion = SELECT1071public.packagesetinclusion = SELECT
1067public.flatpackagesetinclusion = SELECT1072public.flatpackagesetinclusion = SELECT
@@ -1250,6 +1255,7 @@
1250public.personlanguage = SELECT1255public.personlanguage = SELECT
1251public.structuralsubscription = SELECT1256public.structuralsubscription = SELECT
1252public.packageset = SELECT1257public.packageset = SELECT
1258public.packagesetgroup = SELECT
1253public.packagesetsources = SELECT1259public.packagesetsources = SELECT
1254public.packagesetinclusion = SELECT1260public.packagesetinclusion = SELECT
1255public.flatpackagesetinclusion = SELECT1261public.flatpackagesetinclusion = SELECT
@@ -1702,6 +1708,7 @@
1702public.productseries = SELECT1708public.productseries = SELECT
1703public.revision = SELECT1709public.revision = SELECT
1704public.revisionauthor = SELECT, INSERT1710public.revisionauthor = SELECT, INSERT
1711public.seriessourcepackagebranch = SELECT
1705public.sourcepackagename = SELECT1712public.sourcepackagename = SELECT
1706public.staticdiff = SELECT, INSERT1713public.staticdiff = SELECT, INSERT
1707public.teammembership = SELECT1714public.teammembership = SELECT
@@ -1807,6 +1814,7 @@
1807type=user1814type=user
1808public.libraryfilecontent = SELECT1815public.libraryfilecontent = SELECT
1809public.openidrpconfig = SELECT1816public.openidrpconfig = SELECT
1817public.branch = SELECT
18101818
1811[modified-branches]1819[modified-branches]
1812type=user1820type=user
18131821
=== modified file 'database/schema/trusted.sql'
--- database/schema/trusted.sql 2009-08-19 15:35:13 +0000
+++ database/schema/trusted.sql 2009-11-03 18:13:42 +0000
@@ -1029,7 +1029,22 @@
1029 DECLARE1029 DECLARE
1030 parent_name text;1030 parent_name text;
1031 child_name text;1031 child_name text;
1032 parent_distroseries text;
1033 child_distroseries text;
1032 BEGIN1034 BEGIN
1035 -- Make sure that the package sets being associated here belong
1036 -- to the same distro series.
1037 IF (SELECT parent.distroseries != child.distroseries
1038 FROM packageset parent, packageset child
1039 WHERE parent.id = NEW.parent AND child.id = NEW.child)
1040 THEN
1041 SELECT name INTO parent_name FROM packageset WHERE id = NEW.parent;
1042 SELECT name INTO child_name FROM packageset WHERE id = NEW.child;
1043 SELECT ds.name INTO parent_distroseries FROM packageset ps, distroseries ds WHERE ps.id = NEW.parent AND ps.distroseries = ds.id;
1044 SELECT ds.name INTO child_distroseries FROM packageset ps, distroseries ds WHERE ps.id = NEW.child AND ps.distroseries = ds.id;
1045 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;
1046 END IF;
1047
1033 IF EXISTS(1048 IF EXISTS(
1034 SELECT * FROM flatpackagesetinclusion1049 SELECT * FROM flatpackagesetinclusion
1035 WHERE parent = NEW.child AND child = NEW.parent LIMIT 1)1050 WHERE parent = NEW.child AND child = NEW.parent LIMIT 1)
10361051
=== modified file 'database/schema/unautovacuumable.py'
--- database/schema/unautovacuumable.py 2009-06-24 21:17:33 +0000
+++ database/schema/unautovacuumable.py 2009-11-03 18:13:43 +0000
@@ -19,6 +19,7 @@
19# pylint: disable-msg=W040319# pylint: disable-msg=W0403
20import _pythonpath20import _pythonpath
2121
22from distutils.version import LooseVersion
22from optparse import OptionParser23from optparse import OptionParser
23import sys24import sys
24import time25import time
@@ -44,14 +45,31 @@
44 con.set_isolation_level(0) # Autocommit45 con.set_isolation_level(0) # Autocommit
45 cur = con.cursor()46 cur = con.cursor()
4647
48 cur.execute('show server_version')
49 pg_version = LooseVersion(cur.fetchone()[0])
50
47 log.debug("Disabling autovacuum on all tables in the database.")51 log.debug("Disabling autovacuum on all tables in the database.")
48 cur.execute("""52 if pg_version < LooseVersion('8.4.0'):
49 INSERT INTO pg_autovacuum53 cur.execute("""
50 SELECT pg_class.oid, FALSE, -1,-1,-1,-1,-1,-1,-1,-154 INSERT INTO pg_autovacuum
51 FROM pg_class55 SELECT pg_class.oid, FALSE, -1,-1,-1,-1,-1,-1,-1,-1
52 WHERE relkind in ('r','t')56 FROM pg_class
53 AND pg_class.oid NOT IN (SELECT vacrelid FROM pg_autovacuum)57 WHERE relkind in ('r','t')
54 """)58 AND pg_class.oid NOT IN (SELECT vacrelid FROM pg_autovacuum)
59 """)
60 else:
61 cur.execute("""
62 SELECT nspname,relname
63 FROM pg_namespace, pg_class
64 WHERE relnamespace = pg_namespace.oid
65 AND relkind = 'r' AND nspname <> 'pg_catalog'
66 """)
67 for namespace, table in list(cur.fetchall()):
68 cur.execute("""
69 ALTER TABLE ONLY "%s"."%s" SET (
70 autovacuum_enabled=false,
71 toast.autovacuum_enabled=false)
72 """ % (namespace, table))
5573
56 log.debug("Killing existing autovacuum processes")74 log.debug("Killing existing autovacuum processes")
57 num_autovacuums = -175 num_autovacuums = -1
5876
=== modified file 'lib/canonical/config/schema-lazr.conf'
--- lib/canonical/config/schema-lazr.conf 2009-10-29 05:50:08 +0000
+++ lib/canonical/config/schema-lazr.conf 2009-11-03 18:13:40 +0000
@@ -1436,13 +1436,6 @@
14361436
14371437
1438[ppa.master]1438[ppa.master]
1439# Line-separated repository lines to be used as build dependencies, in
1440# the following format:
1441# deb [user:pass@]<host>[/path] %(series)s[-pocket] [components]
1442# 'series' variable is replaced with the series name of the context build.
1443# datatype: string
1444dependencies: none
1445
14461439
1447[poimport]1440[poimport]
1448# The database user which will be used by this process.1441# The database user which will be used by this process.
14491442
=== modified file 'lib/canonical/ftests/pgsql.py'
--- lib/canonical/ftests/pgsql.py 2009-06-25 05:30:52 +0000
+++ lib/canonical/ftests/pgsql.py 2009-11-03 18:13:40 +0000
@@ -144,6 +144,13 @@
144 host = None144 host = None
145 port = None145 port = None
146146
147 # Class attributes. With PostgreSQL 8.4, pg_shdepend bloats
148 # hugely when we drop and create databases, because this
149 # operation cancels any autovacuum process maintaining it.
150 # To cope, we need to manually vacuum it ourselves occasionally.
151 vacuum_shdepend_every = 10
152 _vacuum_shdepend_counter = 0
153
147 # (template, name) of last test. Class attribute.154 # (template, name) of last test. Class attribute.
148 _last_db = (None, None)155 _last_db = (None, None)
149 # Class attribute. True if we should destroy the DB because changes made.156 # Class attribute. True if we should destroy the DB because changes made.
@@ -308,6 +315,10 @@
308 if 'does not exist' in str(x):315 if 'does not exist' in str(x):
309 break316 break
310 raise317 raise
318 PgTestSetup._vacuum_shdepend_counter += 1
319 if (PgTestSetup._vacuum_shdepend_counter
320 % PgTestSetup.vacuum_shdepend_every) == 0:
321 cur.execute('VACUUM pg_catalog.pg_shdepend')
311 finally:322 finally:
312 con.close()323 con.close()
313324
314325
=== modified file 'lib/canonical/launchpad/emailtemplates/branch-merge-proposal-created.txt'
--- lib/canonical/launchpad/emailtemplates/branch-merge-proposal-created.txt 2009-08-05 19:27:17 +0000
+++ lib/canonical/launchpad/emailtemplates/branch-merge-proposal-created.txt 2009-11-03 18:13:39 +0000
@@ -1,4 +1,4 @@
1%(proposal_registrant)s has proposed merging %(source_branch)s into %(target_branch)s.1%(proposal_registrant)s has proposed merging %(source_branch)s into %(target_branch)s%(prerequisite)s.
22
3%(reviews)s%(related_bugs)s%(gap)s%(comment)s%(diff_cutoff_warning)s3%(reviews)s%(related_bugs)s%(gap)s%(comment)s%(diff_cutoff_warning)s
4-- 4--
55
=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
--- lib/canonical/launchpad/icing/style-3-0.css 2009-10-29 21:39:12 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css 2009-11-03 18:13:42 +0000
@@ -466,6 +466,9 @@
466 padding: 0 1.5em 0 0;466 padding: 0 1.5em 0 0;
467 }467 }
468.subordinate {468.subordinate {
469 margin-left: 2em;
470 }
471ol.subordinate {
469 margin-left: 4em;472 margin-left: 4em;
470 }473 }
471.two-column-list li,474.two-column-list li,
472475
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-10-29 19:55:59 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-11-03 18:13:43 +0000
@@ -234,6 +234,14 @@
234 IArchive, 'getAllPublishedBinaries', 'status', PackagePublishingStatus)234 IArchive, 'getAllPublishedBinaries', 'status', PackagePublishingStatus)
235patch_choice_parameter_type(235patch_choice_parameter_type(
236 IArchive, 'getAllPublishedBinaries', 'pocket', PackagePublishingPocket)236 IArchive, 'getAllPublishedBinaries', 'pocket', PackagePublishingPocket)
237patch_plain_parameter_type(
238 IArchive, 'isSourceUploadAllowed', 'distroseries', IDistroSeries)
239patch_plain_parameter_type(
240 IArchive, 'newPackagesetUploader', 'packageset', IPackageset)
241patch_plain_parameter_type(
242 IArchive, 'getUploadersForPackageset', 'packageset', IPackageset)
243patch_plain_parameter_type(
244 IArchive, 'deletePackagesetUploader', 'packageset', IPackageset)
237245
238# IDistribution246# IDistribution
239IDistribution['serieses'].value_type.schema = IDistroSeries247IDistribution['serieses'].value_type.schema = IDistroSeries
@@ -283,6 +291,8 @@
283 IPackageset, 'getSourcesSharedBy', 'other_package_set', IPackageset)291 IPackageset, 'getSourcesSharedBy', 'other_package_set', IPackageset)
284patch_plain_parameter_type(292patch_plain_parameter_type(
285 IPackageset, 'getSourcesNotSharedBy', 'other_package_set', IPackageset)293 IPackageset, 'getSourcesNotSharedBy', 'other_package_set', IPackageset)
294patch_collection_return_type(
295 IPackageset, 'relatedSets', IPackageset)
286296
287# IPackageUpload297# IPackageUpload
288IPackageUpload['pocket'].vocabulary = PackagePublishingPocket298IPackageUpload['pocket'].vocabulary = PackagePublishingPocket
289299
=== modified file 'lib/canonical/launchpad/templates/launchpad-login.pt'
--- lib/canonical/launchpad/templates/launchpad-login.pt 2009-10-16 16:13:00 +0000
+++ lib/canonical/launchpad/templates/launchpad-login.pt 2009-11-03 18:13:39 +0000
@@ -171,13 +171,13 @@
171 <p>171 <p>
172 Creating your Launchpad account is easy. Here's what to do:</p>172 Creating your Launchpad account is easy. Here's what to do:</p>
173173
174 <ol class="subordinate">174 <ol class="subordinate">
175 <li>Make sure cookies are enabled in your browser.</li>175 <li>Make sure cookies are enabled in your browser.</li>
176 <li>Enter your e-mail address and answer our random question176 <li>Enter your e-mail address and answer our random question
177 so we know that you're human.177 so we know that you're human.
178 </li>178 </li>
179 <li>Follow the URL in the confirmation e-mail that Launchpad sends and you're done!</li>179 <li>Follow the URL in the confirmation e-mail that Launchpad sends and you're done!</li>
180 </ol>180 </ol>
181181
182182
183183
184184
=== modified file 'lib/lp/bugs/doc/externalbugtracker-debbugs.txt'
--- lib/lp/bugs/doc/externalbugtracker-debbugs.txt 2009-10-06 07:38:29 +0000
+++ lib/lp/bugs/doc/externalbugtracker-debbugs.txt 2009-11-03 18:13:40 +0000
@@ -102,7 +102,7 @@
102 >>> from lp.bugs.scripts.checkwatches import BugWatchUpdater102 >>> from lp.bugs.scripts.checkwatches import BugWatchUpdater
103 >>> bug_watch_updater = BugWatchUpdater(txn)103 >>> bug_watch_updater = BugWatchUpdater(txn)
104 >>> external_debbugs.sync_comments = False104 >>> external_debbugs.sync_comments = False
105 >>> bug_watch_ids = [bug_watch.id for bug_watch in bug_watches]105 >>> bug_watch_ids = sorted([bug_watch.id for bug_watch in bug_watches])
106 >>> bug_watch_updater.updateBugWatches(external_debbugs, bug_watches)106 >>> bug_watch_updater.updateBugWatches(external_debbugs, bug_watches)
107 INFO:...:Updating 5 watches for 5 bugs on http://...107 INFO:...:Updating 5 watches for 5 bugs on http://...
108108
@@ -110,10 +110,10 @@
110 >>> for bug_watch_id in bug_watch_ids:110 >>> for bug_watch_id in bug_watch_ids:
111 ... bug_watch = getUtility(IBugWatchSet).get(bug_watch_id)111 ... bug_watch = getUtility(IBugWatchSet).get(bug_watch_id)
112 ... print "%s: %s" % (bug_watch.remotebug, bug_watch.remotestatus)112 ... print "%s: %s" % (bug_watch.remotebug, bug_watch.remotestatus)
113 280883: done grave woody security
113 304014: open important114 304014: open important
114 327452: done critical patch security115 327452: done critical patch security
115 327549: open important security116 327549: open important security
116 280883: done grave woody security
117 308994: open important117 308994: open important
118118
119The lastchecked attribute got updated for each bug watch, so no more119The lastchecked attribute got updated for each bug watch, so no more
120120
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2009-10-11 02:33:30 +0000
+++ lib/lp/code/browser/branch.py 2009-11-03 18:13:40 +0000
@@ -575,7 +575,8 @@
575 BranchLifecycleStatus,575 BranchLifecycleStatus,
576 css_class_prefix='branchstatus'),576 css_class_prefix='branchstatus'),
577 'status_value': self.context.lifecycle_status.title,577 'status_value': self.context.lifecycle_status.title,
578 'user_can_edit_status': check_permission('launchpad.Edit', self.context),578 'user_can_edit_status': check_permission(
579 'launchpad.Edit', self.context),
579 'branch_path': '/' + self.context.unique_name,580 'branch_path': '/' + self.context.unique_name,
580 })581 })
581582
@@ -1153,6 +1154,13 @@
1153 description=_(1154 description=_(
1154 "The branch that the source branch will be merged into."))1155 "The branch that the source branch will be merged into."))
11551156
1157 prerequisite_branch = Choice(
1158 title=_('Prerequisite Branch'),
1159 vocabulary='Branch', required=False, readonly=False,
1160 description=_(
1161 'A branch that should be merged before this one. (Its changes'
1162 ' will not be shown in the diff.)'))
1163
1156 comment = Text(1164 comment = Text(
1157 title=_('Initial Comment'), required=False,1165 title=_('Initial Comment'), required=False,
1158 description=_('Describe your change.'))1166 description=_('Describe your change.'))
@@ -1206,6 +1214,7 @@
1206 registrant = self.user1214 registrant = self.user
1207 source_branch = self.context1215 source_branch = self.context
1208 target_branch = data['target_branch']1216 target_branch = data['target_branch']
1217 prerequisite_branch = data.get('prerequisite_branch')
12091218
1210 review_requests = []1219 review_requests = []
1211 reviewer = data.get('reviewer')1220 reviewer = data.get('reviewer')
@@ -1217,7 +1226,8 @@
1217 # and an advanced expandable section.1226 # and an advanced expandable section.
1218 proposal = source_branch.addLandingTarget(1227 proposal = source_branch.addLandingTarget(
1219 registrant=registrant, target_branch=target_branch,1228 registrant=registrant, target_branch=target_branch,
1220 needs_review=True, initial_comment=data.get('comment'),1229 prerequisite_branch=prerequisite_branch, needs_review=True,
1230 initial_comment=data.get('comment'),
1221 review_requests=review_requests)1231 review_requests=review_requests)
12221232
1223 self.next_url = canonical_url(proposal)1233 self.next_url = canonical_url(proposal)
12241234
=== modified file 'lib/lp/code/mail/branchmergeproposal.py'
--- lib/lp/code/mail/branchmergeproposal.py 2009-09-18 13:50:52 +0000
+++ lib/lp/code/mail/branchmergeproposal.py 2009-11-03 18:13:41 +0000
@@ -199,6 +199,7 @@
199 'proposal_registrant': self.merge_proposal.registrant.displayname,199 'proposal_registrant': self.merge_proposal.registrant.displayname,
200 'source_branch': self.merge_proposal.source_branch.bzr_identity,200 'source_branch': self.merge_proposal.source_branch.bzr_identity,
201 'target_branch': self.merge_proposal.target_branch.bzr_identity,201 'target_branch': self.merge_proposal.target_branch.bzr_identity,
202 'prerequisite': '',
202 'proposal_title': self.merge_proposal.title,203 'proposal_title': self.merge_proposal.title,
203 'proposal_url': canonical_url(self.merge_proposal),204 'proposal_url': canonical_url(self.merge_proposal),
204 'edit_subscription': '',205 'edit_subscription': '',
@@ -209,6 +210,10 @@
209 'diff_cutoff_warning': '',210 'diff_cutoff_warning': '',
210 }211 }
211212
213 if self.merge_proposal.prerequisite_branch is not None:
214 prereq_url = self.merge_proposal.prerequisite_branch.bzr_identity
215 params['prerequisite'] = ' with %s as a prerequisite' % prereq_url
216
212 requested_reviews = []217 requested_reviews = []
213 for review in self.requested_reviews:218 for review in self.requested_reviews:
214 reviewer = review.reviewer219 reviewer = review.reviewer
215220
=== modified file 'lib/lp/code/mail/tests/test_branchmergeproposal.py'
--- lib/lp/code/mail/tests/test_branchmergeproposal.py 2009-09-18 13:50:52 +0000
+++ lib/lp/code/mail/tests/test_branchmergeproposal.py 2009-11-03 18:13:42 +0000
@@ -36,7 +36,7 @@
36 super(TestMergeProposalMailing, self).setUp('admin@canonical.com')36 super(TestMergeProposalMailing, self).setUp('admin@canonical.com')
3737
38 def makeProposalWithSubscriber(self, diff_text=None,38 def makeProposalWithSubscriber(self, diff_text=None,
39 initial_comment=None):39 initial_comment=None, prerequisite=False):
40 if diff_text is not None:40 if diff_text is not None:
41 preview_diff = PreviewDiff.create(41 preview_diff = PreviewDiff.create(
42 diff_text,42 diff_text,
@@ -49,9 +49,14 @@
49 registrant = self.factory.makePerson(49 registrant = self.factory.makePerson(
50 name='bazqux', displayname='Baz Qux', email='baz.qux@example.com')50 name='bazqux', displayname='Baz Qux', email='baz.qux@example.com')
51 product = self.factory.makeProduct(name='super-product')51 product = self.factory.makeProduct(name='super-product')
52 if prerequisite:
53 prerequisite_branch = self.factory.makeProductBranch(product)
54 else:
55 prerequisite_branch = None
52 bmp = self.factory.makeBranchMergeProposal(56 bmp = self.factory.makeBranchMergeProposal(
53 registrant=registrant, product=product, preview_diff=preview_diff,57 registrant=registrant, product=product,
54 initial_comment=initial_comment)58 prerequisite_branch=prerequisite_branch,
59 preview_diff=preview_diff, initial_comment=initial_comment)
55 subscriber = self.factory.makePerson(displayname='Baz Quxx',60 subscriber = self.factory.makePerson(displayname='Baz Quxx',
56 email='baz.quxx@example.com')61 email='baz.quxx@example.com')
57 bmp.source_branch.subscribe(subscriber,62 bmp.source_branch.subscribe(subscriber,
@@ -133,6 +138,14 @@
133 'Requested reviews:\n Review-person (review-person)\n\n-- \n',138 'Requested reviews:\n Review-person (review-person)\n\n-- \n',
134 ctrl.body)139 ctrl.body)
135140
141 def test_forCreation_with_prerequisite_branch(self):
142 """Correctly format list of reviewers."""
143 bmp, subscriber = self.makeProposalWithSubscriber(prerequisite=True)
144 mailer = BMPMailer.forCreation(bmp, bmp.registrant)
145 ctrl = mailer.generateEmail('baz.quxx@example.com', subscriber)
146 prereq = bmp.prerequisite_branch.bzr_identity
147 self.assertIn(' with %s as a prerequisite.' % prereq, ctrl.body)
148
136 def test_to_addrs_includes_reviewers(self):149 def test_to_addrs_includes_reviewers(self):
137 """The addresses for the to header include requested reviewers"""150 """The addresses for the to header include requested reviewers"""
138 request, requester = self.makeReviewRequest()151 request, requester = self.makeReviewRequest()
139152
=== modified file 'lib/lp/code/stories/branches/xx-branch-merge-proposals.txt'
--- lib/lp/code/stories/branches/xx-branch-merge-proposals.txt 2009-10-23 02:36:17 +0000
+++ lib/lp/code/stories/branches/xx-branch-merge-proposals.txt 2009-11-03 18:13:42 +0000
@@ -57,6 +57,9 @@
57 >>> nopriv_browser.getControl(57 >>> nopriv_browser.getControl(
58 ... name='field.target_branch.target_branch').value = (58 ... name='field.target_branch.target_branch').value = (
59 ... '~name12/gnome-terminal/main')59 ... '~name12/gnome-terminal/main')
60 >>> nopriv_browser.getControl(
61 ... name='field.prerequisite_branch').value = (
62 ... '~name12/gnome-terminal/pushed')
6063
61There is a cancel link shown with the buttons.64There is a cancel link shown with the buttons.
6265
@@ -68,6 +71,20 @@
68 >>> print nopriv_browser.url71 >>> print nopriv_browser.url
69 http://code.launchpad.dev/~name12/gnome-terminal/klingon/+merge/...72 http://code.launchpad.dev/~name12/gnome-terminal/klingon/+merge/...
7073
74The summary reflects the selected target and prerequisite.
75
76 >>> def print_summary(browser):
77 ... print extract_text(find_tag_by_id(
78 ... browser.contents, 'proposal-summary'))
79 >>> print_summary(nopriv_browser)
80 Status:...
81 Proposed branch:
82 lp://dev/~name12/gnome-terminal/klingon
83 Merge into:
84 lp://dev/~name12/gnome-terminal/main
85 Prerequisite:
86 lp://dev/~name12/gnome-terminal/pushed
87
7188
72Editing a commit message89Editing a commit message
73------------------------90------------------------
@@ -83,9 +100,6 @@
83 >>> print nopriv_browser.url100 >>> print nopriv_browser.url
84 http://code.launchpad.dev/~name12/gnome-terminal/klingon/+merge/1101 http://code.launchpad.dev/~name12/gnome-terminal/klingon/+merge/1
85102
86 >>> def print_summary(browser):
87 ... print extract_text(find_tag_by_id(
88 ... browser.contents, 'proposal-summary'))
89 >>> print_tag_with_id(nopriv_browser.contents, 'edit-description')103 >>> print_tag_with_id(nopriv_browser.contents, 'edit-description')
90 Commit Message104 Commit Message
91 Add more &lt;b&gt;mojo&lt;/b&gt;105 Add more &lt;b&gt;mojo&lt;/b&gt;
92106
=== modified file 'lib/lp/code/templates/branchmergeproposal-pagelet-summary.pt'
--- lib/lp/code/templates/branchmergeproposal-pagelet-summary.pt 2009-10-21 23:20:59 +0000
+++ lib/lp/code/templates/branchmergeproposal-pagelet-summary.pt 2009-11-03 18:13:43 +0000
@@ -105,6 +105,10 @@
105 <th>Merge into:</th>105 <th>Merge into:</th>
106 <td tal:content="structure context/target_branch/fmt:bzr-link">lp:~foo/bar/baz</td>106 <td tal:content="structure context/target_branch/fmt:bzr-link">lp:~foo/bar/baz</td>
107 </tr>107 </tr>
108 <tr tal:condition="context/prerequisite_branch">
109 <th>Prerequisite:</th>
110 <td tal:content="structure context/prerequisite_branch/fmt:bzr-link">lp:~foo/bar/baz</td>
111 </tr>
108 <tr tal:condition="context/preview_diff">112 <tr tal:condition="context/preview_diff">
109 <th>Diff against target:</th>113 <th>Diff against target:</th>
110 <td>114 <td>
111115
=== modified file 'lib/lp/code/templates/branchmergeproposal-resubmit.pt'
--- lib/lp/code/templates/branchmergeproposal-resubmit.pt 2009-09-08 19:24:40 +0000
+++ lib/lp/code/templates/branchmergeproposal-resubmit.pt 2009-11-03 18:13:39 +0000
@@ -14,8 +14,9 @@
14 <div metal:fill-slot="extra_info">14 <div metal:fill-slot="extra_info">
15 <p>15 <p>
16 Resubmitting this proposal to merge will cause this proposal to be16 Resubmitting this proposal to merge will cause this proposal to be
17 marked as <strong>superseded</strong>. Another proposal to merge with17 marked as <strong>superseded</strong>. Another merge proposal will
18 the same source and target branches will be created.18 be created, with the same source, target and prerequisite branch
19 (if any).
19 </p>20 </p>
20 <p>21 <p>
21 Everyone who has reviewed the previous proposal or was requested to22 Everyone who has reviewed the previous proposal or was requested to
2223
=== modified file 'lib/lp/registry/browser/__init__.py'
--- lib/lp/registry/browser/__init__.py 2009-09-22 16:21:12 +0000
+++ lib/lp/registry/browser/__init__.py 2009-11-03 18:13:42 +0000
@@ -18,6 +18,8 @@
1818
19from zope.component import getUtility19from zope.component import getUtility
2020
21from storm.store import Store
22
21from lp.bugs.interfaces.bugtask import BugTaskSearchParams, IBugTaskSet23from lp.bugs.interfaces.bugtask import BugTaskSearchParams, IBugTaskSet
22from lp.registry.interfaces.productseries import IProductSeries24from lp.registry.interfaces.productseries import IProductSeries
23from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities25from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
@@ -136,15 +138,22 @@
136 """The context's URL."""138 """The context's URL."""
137 return canonical_url(self.context)139 return canonical_url(self.context)
138140
139 def _getBugtasks(self, milestone):141 def _getBugtasks(self, target):
140 """Return the list `IBugTask`s targeted to the milestone."""142 """Return the list `IBugTask`s associated with the target."""
141 params = BugTaskSearchParams(milestone=milestone, user=None)143 if IProductSeries.providedBy(target):
144 params = BugTaskSearchParams(user=None)
145 params.setProductSeries(target)
146 else:
147 params = BugTaskSearchParams(milestone=target, user=None)
142 bugtasks = getUtility(IBugTaskSet).search(params)148 bugtasks = getUtility(IBugTaskSet).search(params)
143 return list(bugtasks)149 return list(bugtasks)
144150
145 def _getSpecifications(self, milestone):151 def _getSpecifications(self, target):
146 """Return the list `ISpecification`s targeted to the milestone."""152 """Return the list `ISpecification`s associated to the target."""
147 return list(milestone.specifications)153 if IProductSeries.providedBy(target):
154 return list(target.all_specifications)
155 else:
156 return list(target.specifications)
148157
149 def _getProductRelease(self, milestone):158 def _getProductRelease(self, milestone):
150 """The `IProductRelease` associated with the milestone."""159 """The `IProductRelease` associated with the milestone."""
@@ -158,10 +167,37 @@
158 else:167 else:
159 return []168 return []
160169
170 def _unsubscribe_structure(self, structure):
171 """Removed the subscriptions from structure."""
172 for subscription in structure.getSubscriptions():
173 # The owner of the subscription or an admin are the only users
174 # that can destroy a subscription, but this rule cannot prevent
175 # the owner from removing the structure.
176 Store.of(subscription).remove(subscription)
177
178 def _remove_series_bugs_and_specifications(self, series):
179 """Untarget the associated bugs and subscriptions."""
180 for spec in self._getSpecifications(series):
181 spec.proposeGoal(None, self.user)
182 for bugtask in self._getBugtasks(series):
183 # Bugtasks cannot be deleted directly. In this case, the bugtask
184 # is already reported on the product, so the series bugtask has
185 # no purpose without a series.
186 Store.of(bugtask).remove(bugtask)
187
161 def _deleteProductSeries(self, series):188 def _deleteProductSeries(self, series):
162 """Remove the series and delete/unlink related objects."""189 """Remove the series and delete/unlink related objects.
163 # Delete all milestones, releases, and files.190
164 # Any associated bugtasks and specifications are untargeted.191 All subordinate milestones, releases, and files will be deleted.
192 Milestone bugs and blueprints will be untargeted.
193 Series bugs and blueprints will be untargeted.
194 Series and milestone structural subscriptions are unsubscribed.
195 Series branches are unlinked.
196 """
197 self._unsubscribe_structure(series)
198 self._remove_series_bugs_and_specifications(series)
199 series.branch = None
200
165 for milestone in series.all_milestones:201 for milestone in series.all_milestones:
166 self._deleteMilestone(milestone)202 self._deleteMilestone(milestone)
167 # Series are not deleted because some objects like translations are203 # Series are not deleted because some objects like translations are
@@ -174,6 +210,7 @@
174210
175 def _deleteMilestone(self, milestone):211 def _deleteMilestone(self, milestone):
176 """Delete a milestone and unlink related objects."""212 """Delete a milestone and unlink related objects."""
213 self._unsubscribe_structure(milestone)
177 for bugtask in self._getBugtasks(milestone):214 for bugtask in self._getBugtasks(milestone):
178 bugtask.milestone = None215 bugtask.milestone = None
179 for spec in self._getSpecifications(milestone):216 for spec in self._getSpecifications(milestone):
@@ -191,6 +228,7 @@
191228
192class RegistryEditFormView(LaunchpadEditFormView):229class RegistryEditFormView(LaunchpadEditFormView):
193 """A base class that provides consistent edit form behaviour."""230 """A base class that provides consistent edit form behaviour."""
231
194 @property232 @property
195 def page_title(self):233 def page_title(self):
196 """The page title."""234 """The page title."""
197235
=== modified file 'lib/lp/registry/browser/productseries.py'
--- lib/lp/registry/browser/productseries.py 2009-10-26 19:47:59 +0000
+++ lib/lp/registry/browser/productseries.py 2009-11-03 18:13:42 +0000
@@ -479,7 +479,7 @@
479 @cachedproperty479 @cachedproperty
480 def bugtasks(self):480 def bugtasks(self):
481 """A list of all `IBugTask`s targeted to this series."""481 """A list of all `IBugTask`s targeted to this series."""
482 all_bugtasks = []482 all_bugtasks = self._getBugtasks(self.context)
483 for milestone in self.milestones:483 for milestone in self.milestones:
484 all_bugtasks.extend(self._getBugtasks(milestone))484 all_bugtasks.extend(self._getBugtasks(milestone))
485 return all_bugtasks485 return all_bugtasks
@@ -487,7 +487,7 @@
487 @cachedproperty487 @cachedproperty
488 def specifications(self):488 def specifications(self):
489 """A list of all `ISpecification`s targeted to this series."""489 """A list of all `ISpecification`s targeted to this series."""
490 all_specifications = []490 all_specifications = self._getSpecifications(self.context)
491 for milestone in self.milestones:491 for milestone in self.milestones:
492 all_specifications.extend(self._getSpecifications(milestone))492 all_specifications.extend(self._getSpecifications(milestone))
493 return all_specifications493 return all_specifications
@@ -497,6 +497,11 @@
497 """Does the series have any targeted bugtasks or specifications."""497 """Does the series have any targeted bugtasks or specifications."""
498 return len(self.bugtasks) > 0 or len(self.specifications) > 0498 return len(self.bugtasks) > 0 or len(self.specifications) > 0
499499
500 @property
501 def has_linked_branch(self):
502 """Is the series linked to a branch."""
503 return self.context.branch is not None
504
500 @cachedproperty505 @cachedproperty
501 def product_release_files(self):506 def product_release_files(self):
502 """A list of all `IProductReleaseFile`s that belong to this series."""507 """A list of all `IProductReleaseFile`s that belong to this series."""
503508
=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
--- lib/lp/registry/browser/tests/milestone-views.txt 2009-09-22 16:21:12 +0000
+++ lib/lp/registry/browser/tests/milestone-views.txt 2009-11-03 18:13:41 +0000
@@ -654,6 +654,9 @@
654 >>> bug = factory.makeBug(product=firefox)654 >>> bug = factory.makeBug(product=firefox)
655 >>> bugtask = bug.bugtasks[0]655 >>> bugtask = bug.bugtasks[0]
656 >>> bugtask.milestone = milestone656 >>> bugtask.milestone = milestone
657 >>> subscription = milestone.addSubscription(owner, owner)
658 >>> [subscription for subscription in owner.structural_subscriptions]
659 [<StructuralSubscription ...>]
657660
658 >>> view = create_initialized_view(milestone, '+delete')661 >>> view = create_initialized_view(milestone, '+delete')
659 >>> [bugtask.milestone.name for bugtask in view.bugtasks]662 >>> [bugtask.milestone.name for bugtask in view.bugtasks]
@@ -685,6 +688,9 @@
685 >>> print bugtask.milestone688 >>> print bugtask.milestone
686 None689 None
687690
691 >>> [subscription for subscription in owner.structural_subscriptions]
692 []
693
688No Privileges Person cannot access this view because he is neither the694No Privileges Person cannot access this view because he is neither the
689project owner or series driver..695project owner or series driver..
690696
691697
=== modified file 'lib/lp/registry/browser/tests/productseries-views.txt'
--- lib/lp/registry/browser/tests/productseries-views.txt 2009-10-23 16:21:47 +0000
+++ lib/lp/registry/browser/tests/productseries-views.txt 2009-11-03 18:13:42 +0000
@@ -229,6 +229,8 @@
229 []229 []
230 >>> view.product_release_files230 >>> view.product_release_files
231 []231 []
232 >>> view.has_linked_branch
233 False
232234
233Most series that are deleted do not have any related objects, but a small235Most series that are deleted do not have any related objects, but a small
234portion do.236portion do.
@@ -244,18 +246,38 @@
244 >>> bugtask = bug.bugtasks[0]246 >>> bugtask = bug.bugtasks[0]
245 >>> bugtask.milestone = milestone_two247 >>> bugtask.milestone = milestone_two
246248
249 >>> owner = product.owner
250 >>> series_specification = factory.makeSpecification(product=product)
251 >>> series_specification.proposeGoal(productseries, owner)
252 >>> series_bugtask = factory.makeBugTask(bug=bug, target=productseries)
253 >>> subscription = productseries.addSubscription(owner, owner)
254 >>> productseries.branch = factory.makeBranch()
255
247 >>> view = create_view(productseries, name='+delete')256 >>> view = create_view(productseries, name='+delete')
248 >>> [milestone.name for milestone in view.milestones]257 >>> [milestone.name for milestone in view.milestones]
249 [u'0.2', u'0.1']258 [u'0.2', u'0.1']
250 >>> view.has_bugtasks_and_specifications259 >>> view.has_bugtasks_and_specifications
251 True260 True
252 >>> [bugtask.milestone.name for bugtask in view.bugtasks]261 >>> for bugtask in view.bugtasks:
253 [u'0.2']262 ... if bugtask.milestone is not None:
254 >>> [spec.milestone.name for spec in view.specifications]263 ... print bugtask.milestone.name
255 [u'0.1']264 ... else:
256265 ... print bugtask.target.name
257 # Listing and deleting product release files is done in the story266 rabbit
258 # because they require the Librarian to be running.267 0.2
268 >>> for spec in view.specifications:
269 ... if spec.milestone is not None:
270 ... print spec.milestone.name
271 ... else:
272 ... print spec.goal.name
273 rabbit
274 0.1
275
276 >>> view.has_linked_branch
277 True
278
279 # Listing and deleting product release files is done in
280 # product-release-views because they require the Librarian to be running.
259281
260Series that are the active focus of development cannot be deleted. The282Series that are the active focus of development cannot be deleted. The
261view's can_delete property checks this rule.283view's can_delete property checks this rule.
@@ -291,7 +313,8 @@
291Calling the view's delete action on a series that can be deleted will313Calling the view's delete action on a series that can be deleted will
292untarget the bugtasks and specifications that are targeted to the314untarget the bugtasks and specifications that are targeted to the
293series' milestones. The milestones, releases, and release files are315series' milestones. The milestones, releases, and release files are
294deleted.316deleted. Bugs and blueprints targeted to the series are unassigned.
317Series structural subscriptions are removed. Branch links are removed.
295318
296 >>> view = create_initialized_view(productseries, '+delete', form=form)319 >>> view = create_initialized_view(productseries, '+delete', form=form)
297 >>> for notification in view.request.response.notifications:320 >>> for notification in view.request.response.notifications:
@@ -308,11 +331,17 @@
308 None331 None
309 >>> print bugtask.milestone332 >>> print bugtask.milestone
310 None333 None
334 >>> bugtask.related_tasks
335 []
336 >>> print series_specification.milestone
337 None
338 >>> [subscription for subscription in owner.structural_subscriptions]
339 []
311340
312The series was not actually deleted because there are problematic objects341The series was not actually deleted because there are problematic objects
313like translations. The series are assigned to the Obsolete Junk project.342like translations. The series are assigned to the Obsolete Junk project.
314The series name is changed to 'product_name-series_name-date_created' to343The series name is changed to 'product_name-series_name-date_created' to
315avoid conflicts.344avoid conflicts. The linked branch is removed.
316345
317 >>> from zope.component import getUtility346 >>> from zope.component import getUtility
318 >>> from canonical.launchpad.interfaces.launchpad import (347 >>> from canonical.launchpad.interfaces.launchpad import (
319348
=== modified file 'lib/lp/registry/doc/milestone.txt'
--- lib/lp/registry/doc/milestone.txt 2009-08-13 19:03:36 +0000
+++ lib/lp/registry/doc/milestone.txt 2009-11-03 18:13:40 +0000
@@ -496,3 +496,13 @@
496 ...496 ...
497 AssertionError: You cannot delete a milestone which has specifications497 AssertionError: You cannot delete a milestone which has specifications
498 targeted to it.498 targeted to it.
499
500If a milestone has a structural subscription, it cannot be deleted.
501
502 >>> milestone = ff_onedotzero.newMilestone('1.0.14')
503 >>> subscription = milestone.addSubscription(owner, owner)
504 >>> milestone.destroySelf()
505 Traceback (most recent call last):
506 ...
507 AssertionError: You cannot delete a milestone which has structural
508 subscriptions.
499509
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2009-10-23 16:20:14 +0000
+++ lib/lp/registry/model/distribution.py 2009-11-03 18:13:43 +0000
@@ -851,6 +851,7 @@
851 SourcePackagePublishingHistory.dateremoved is NULL851 SourcePackagePublishingHistory.dateremoved is NULL
852 """ % sqlvalues(self, archive),852 """ % sqlvalues(self, archive),
853 distinct=True,853 distinct=True,
854 orderBy="name",
854 clauseTables=['SourcePackagePublishingHistory', 'DistroSeries',855 clauseTables=['SourcePackagePublishingHistory', 'DistroSeries',
855 'SourcePackageRelease']))856 'SourcePackageRelease']))
856857
857858
=== modified file 'lib/lp/registry/model/milestone.py'
--- lib/lp/registry/model/milestone.py 2009-10-22 09:43:29 +0000
+++ lib/lp/registry/model/milestone.py 2009-11-03 18:13:43 +0000
@@ -188,6 +188,9 @@
188 """See `IMilestone`."""188 """See `IMilestone`."""
189 params = BugTaskSearchParams(milestone=self, user=None)189 params = BugTaskSearchParams(milestone=self, user=None)
190 bugtasks = getUtility(IBugTaskSet).search(params)190 bugtasks = getUtility(IBugTaskSet).search(params)
191 assert len(self.getSubscriptions()) == 0, (
192 "You cannot delete a milestone which has structural "
193 "subscriptions.")
191 assert bugtasks.count() == 0, (194 assert bugtasks.count() == 0, (
192 "You cannot delete a milestone which has bugtasks targeted "195 "You cannot delete a milestone which has bugtasks targeted "
193 "to it.")196 "to it.")
@@ -238,6 +241,7 @@
238 """See lp.registry.interfaces.milestone.IMilestoneSet."""241 """See lp.registry.interfaces.milestone.IMilestoneSet."""
239 return Milestone.selectBy(active=True, orderBy='id')242 return Milestone.selectBy(active=True, orderBy='id')
240243
244
241class ProjectMilestone(HasBugsBase):245class ProjectMilestone(HasBugsBase):
242 """A virtual milestone implementation for project.246 """A virtual milestone implementation for project.
243247
244248
=== 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-03 18:13:40 +0000
@@ -219,7 +219,7 @@
219 </tal:release>219 </tal:release>
220220
221 <p class="alternate">221 <p class="alternate">
222 <a class="sprint info"222 <a class="sprite info"
223 tal:define="link overview_menu/downloads"223 tal:define="link overview_menu/downloads"
224 tal:condition="release"224 tal:condition="release"
225 tal:attributes="href link/fmt:url;225 tal:attributes="href link/fmt:url;
226226
=== modified file 'lib/lp/registry/templates/productseries-delete.pt'
--- lib/lp/registry/templates/productseries-delete.pt 2009-08-11 21:31:51 +0000
+++ lib/lp/registry/templates/productseries-delete.pt 2009-11-03 18:13:43 +0000
@@ -30,10 +30,12 @@
30 </tal:no-files>30 </tal:no-files>
31 </p>31 </p>
3232
33 <ul id="milestones" tal:condition="view/milestones">33 <ul id="milestones" class="subordinate"
34 tal:condition="view/milestones">
34 <li tal:repeat="milestone view/milestones">35 <li tal:repeat="milestone view/milestones">
35 <strong>36 <strong>
36 <a tal:attributes="href milestone/fmt:url"><tal:name37 <a class="sprite milestone"
38 tal:attributes="href milestone/fmt:url"><tal:name
37 content="milestone/name">0.9</tal:name><tal:codename39 content="milestone/name">0.9</tal:name><tal:codename
38 condition="milestone/code_name">40 condition="milestone/code_name">
39 "<tal:name41 "<tal:name
@@ -42,9 +44,8 @@
42 </li>44 </li>
43 </ul>45 </ul>
4446
4547 <ul id="files" class="subordinate"
4648 tal:condition="view/product_release_files">
47 <ul id="files" tal:condition="view/product_release_files">
48 <li tal:repeat="file view/product_release_files">49 <li tal:repeat="file view/product_release_files">
49 <strong tal:content="file/libraryfile/filename">foo.tgz</strong>50 <strong tal:content="file/libraryfile/filename">foo.tgz</strong>
50 </li>51 </li>
@@ -54,7 +55,7 @@
54 The following bugs and blueprints will be <em>untargeted</em>:55 The following bugs and blueprints will be <em>untargeted</em>:
55 </p>56 </p>
5657
57 <ul id="bugtasks-and-blueprints"58 <ul id="bugtasks-and-blueprints" class="subordinate"
58 tal:condition="view/has_bugtasks_and_specifications">59 tal:condition="view/has_bugtasks_and_specifications">
59 <li tal:repeat="bugtask view/bugtasks"60 <li tal:repeat="bugtask view/bugtasks"
60 tal:content="structure bugtask/bug/fmt:link">bug 161 tal:content="structure bugtask/bug/fmt:link">bug 1
@@ -64,6 +65,11 @@
64 </li>65 </li>
65 </ul>66 </ul>
6667
68 <p tal:condition="view/has_linked_branch">
69 The associated branch will be <em>unlinked</em>:
70 <a tal:replace="structure view/context/branch/fmt:link" />
71 </p>
72
67 <p>73 <p>
68 Series deletion is permanent.74 Series deletion is permanent.
69 </p>75 </p>
7076
=== modified file 'lib/lp/soyuz/adapters/archivedependencies.py'
--- lib/lp/soyuz/adapters/archivedependencies.py 2009-08-16 12:38:12 +0000
+++ lib/lp/soyuz/adapters/archivedependencies.py 2009-11-03 18:13:42 +0000
@@ -36,7 +36,6 @@
36 'pocket_dependencies',36 'pocket_dependencies',
37 ]37 ]
3838
39from canonical.config import config
40from lp.registry.interfaces.pocket import (39from lp.registry.interfaces.pocket import (
41 PackagePublishingPocket, pocketsuffix)40 PackagePublishingPocket, pocketsuffix)
42from lp.soyuz.interfaces.archive import ArchivePurpose, ALLOW_RELEASE_BUILDS41from lp.soyuz.interfaces.archive import ArchivePurpose, ALLOW_RELEASE_BUILDS
@@ -161,11 +160,9 @@
161160
162 # Append external sources_list lines for this archive if it's161 # Append external sources_list lines for this archive if it's
163 # specified in the configuration.162 # specified in the configuration.
164 archive_config_key = 'ppa.%s_%s' % (163 dependencies = build.archive.external_dependencies
165 build.archive.owner.name, build.archive.name)164 if dependencies is not None:
166 if archive_config_key in config:165 for archive_dep in dependencies.splitlines():
167 archive_config = config[archive_config_key]
168 for archive_dep in archive_config.dependencies.splitlines():
169 line = archive_dep % (166 line = archive_dep % (
170 {'series': build.distroarchseries.distroseries.name})167 {'series': build.distroarchseries.distroseries.name})
171 sources_list_lines.append(line)168 sources_list_lines.append(line)
172169
=== modified file 'lib/lp/soyuz/browser/archive.py'
--- lib/lp/soyuz/browser/archive.py 2009-10-30 12:29:32 +0000
+++ lib/lp/soyuz/browser/archive.py 2009-11-03 18:13:43 +0000
@@ -28,6 +28,7 @@
2828
29from datetime import datetime, timedelta29from datetime import datetime, timedelta
30import pytz30import pytz
31from urlparse import urlparse
3132
32from zope.app.form.browser import TextAreaWidget33from zope.app.form.browser import TextAreaWidget
33from zope.component import getUtility34from zope.component import getUtility
@@ -1775,7 +1776,10 @@
1775class ArchiveAdminView(BaseArchiveEditView):1776class ArchiveAdminView(BaseArchiveEditView):
17761777
1777 field_names = ['enabled', 'private', 'require_virtualized',1778 field_names = ['enabled', 'private', 'require_virtualized',
1778 'buildd_secret', 'authorized_size', 'relative_build_score']1779 'buildd_secret', 'authorized_size', 'relative_build_score',
1780 'external_dependencies']
1781
1782 custom_widget('external_dependencies', TextAreaWidget, height=3)
17791783
1780 def validate_save(self, action, data):1784 def validate_save(self, action, data):
1781 """Validate the save action on ArchiveAdminView.1785 """Validate the save action on ArchiveAdminView.
@@ -1794,12 +1798,44 @@
1794 self.setFieldError(1798 self.setFieldError(
1795 'private',1799 'private',
1796 'Private teams may not have public archives.')1800 'Private teams may not have public archives.')
1797
1798 elif data.get('buildd_secret') is not None and not data['private']:1801 elif data.get('buildd_secret') is not None and not data['private']:
1799 self.setFieldError(1802 self.setFieldError(
1800 'buildd_secret',1803 'buildd_secret',
1801 'Do not specify for non-private archives')1804 'Do not specify for non-private archives')
18021805
1806 # Check the external_dependencies field.
1807 ext_deps = data.get('external_dependencies')
1808 if ext_deps is not None:
1809 errors = self.validate_external_dependencies(ext_deps)
1810 if len(errors) != 0:
1811 error_text = "\n".join(errors)
1812 self.setFieldError('external_dependencies', error_text)
1813
1814 def validate_external_dependencies(self, ext_deps):
1815 """Validate the external_dependencies field.
1816
1817 :param ext_deps: The dependencies form field to check.
1818 """
1819 errors = []
1820 # The field can consist of multiple entries separated by
1821 # newlines, so process each in turn.
1822 for dep in ext_deps.splitlines():
1823 try:
1824 deb, url, suite, components = dep.split(" ", 3)
1825 except ValueError:
1826 errors.append(
1827 "'%s' is not a complete and valid sources.list entry"
1828 % dep)
1829 continue
1830
1831 if deb != "deb":
1832 errors.append("%s: Must start with 'deb'" % dep)
1833 url_components = urlparse(url)
1834 if not url_components[0] or not url_components[1]:
1835 errors.append("%s: Invalid URL" % dep)
1836
1837 return errors
1838
1803 @property1839 @property
1804 def owner_is_private_team(self):1840 def owner_is_private_team(self):
1805 """Is the owner a private team?1841 """Is the owner a private team?
18061842
=== modified file 'lib/lp/soyuz/browser/configure.zcml'
--- lib/lp/soyuz/browser/configure.zcml 2009-10-22 10:33:00 +0000
+++ lib/lp/soyuz/browser/configure.zcml 2009-11-03 18:13:42 +0000
@@ -783,7 +783,7 @@
783 />783 />
784 <browser:url784 <browser:url
785 for="lp.soyuz.interfaces.packageset.IPackageset"785 for="lp.soyuz.interfaces.packageset.IPackageset"
786 path_expression="name"786 path_expression="string:${distroseries/name}/${name}"
787 parent_utility="lp.soyuz.interfaces.packageset.IPackagesetSet"787 parent_utility="lp.soyuz.interfaces.packageset.IPackagesetSet"
788 />788 />
789 <browser:url789 <browser:url
790790
=== modified file 'lib/lp/soyuz/browser/packageset.py'
--- lib/lp/soyuz/browser/packageset.py 2009-06-30 16:56:07 +0000
+++ lib/lp/soyuz/browser/packageset.py 2009-11-03 18:13:42 +0000
@@ -17,3 +17,22 @@
17class PackagesetSetNavigation(GetitemNavigation):17class PackagesetSetNavigation(GetitemNavigation):
18 """Navigation methods for PackagesetSet."""18 """Navigation methods for PackagesetSet."""
19 usedfor = IPackagesetSet19 usedfor = IPackagesetSet
20
21 def traverse(self, distroseries):
22 """Traverse package sets in distro series context.
23
24 The URI fragment of interest is:
25
26 /package-sets/lucid/mozilla
27
28 where 'lucid' is the distro series and 'mozilla' is the package set
29 *name* respectively.
30 """
31 if self.request.stepstogo:
32 # The package set name follows after the distro series.
33 ps_name = self.request.stepstogo.consume()
34 return self.context.getByName(ps_name, distroseries=distroseries)
35
36 # Otherwise return None (to trigger a NotFound error).
37 return None
38
2039
=== modified file 'lib/lp/soyuz/browser/tests/archive-views.txt'
--- lib/lp/soyuz/browser/tests/archive-views.txt 2009-10-30 21:42:10 +0000
+++ lib/lp/soyuz/browser/tests/archive-views.txt 2009-11-03 18:13:41 +0000
@@ -1345,3 +1345,50 @@
13451345
1346 >>> print copy.status.name1346 >>> print copy.status.name
1347 ACCEPTED1347 ACCEPTED
1348
1349
1350== External dependencies validation ==
1351
1352The ArchiveAdminView checks the external_dependencies form data to see if
1353it's a valid sources.list entry.
1354
1355 >>> ppa_archive_view = create_initialized_view(
1356 ... cprov.archive, name="+admin")
1357
1358The validate_external_dependencies() method is called when validating and will
1359return a list of errors if the data dis not validate. A valid entry is of the
1360form:
1361 deb scheme://domain/ suite component[s]
1362
1363 >>> print ppa_archive_view.validate_external_dependencies(
1364 ... "deb http://example.com/ karmic main")
1365 []
1366
1367Multiple entries are valid, separated by newlines:
1368
1369 >>> print ppa_archive_view.validate_external_dependencies(
1370 ... "deb http://example.com/ karmic main\n"
1371 ... "deb http://example.com/ karmic restricted")
1372 []
1373
1374If the line does not start with the word "deb" it fails:
1375
1376 >>> print ppa_archive_view.validate_external_dependencies(
1377 ... "deb http://example.com/ karmic universe\n"
1378 ... "dab http://example.com/ karmic main")
1379 ["dab http://example.com/ karmic main: Must start with 'deb'"]
1380
1381If the line has too few parts it fails. Here we're missing a suite:
1382
1383 >>> print ppa_archive_view.validate_external_dependencies(
1384 ... "deb http://example.com/ karmic universe\n"
1385 ... "deb http://example.com/ main")
1386 ["'deb http://example.com/ main'
1387 is not a complete and valid sources.list entry"]
1388
1389If the URL looks invalid, it fails:
1390
1391 >>> print ppa_archive_view.validate_external_dependencies(
1392 ... "deb http://example.com/ karmic universe\n"
1393 ... "deb example.com/ karmic main")
1394 ['deb example.com/ karmic main: Invalid URL']
13481395
=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml 2009-10-28 14:42:40 +0000
+++ lib/lp/soyuz/configure.zcml 2009-11-03 18:13:42 +0000
@@ -399,8 +399,8 @@
399 <require399 <require
400 permission="launchpad.Commercial"400 permission="launchpad.Commercial"
401 set_attributes="authorized_size buildd_secret401 set_attributes="authorized_size buildd_secret
402 enabled private require_virtualized402 enabled external_dependencies private
403 relative_build_score"/>403 require_virtualized relative_build_score "/>
404 <require404 <require
405 permission="launchpad.Admin"405 permission="launchpad.Admin"
406 set_attributes="distribution name signing_key"/>406 set_attributes="distribution name signing_key"/>
@@ -856,4 +856,12 @@
856 new"/>856 new"/>
857 </securedutility>857 </securedutility>
858858
859 <!-- PackagesetGroup -->
860 <class
861 class="lp.soyuz.model.packagesetgroup.PackagesetGroup">
862 <allow
863 interface="lp.soyuz.interfaces.packagesetgroup.IPackagesetGroup"/>
864 </class>
865
866
859</configure>867</configure>
860868
=== modified file 'lib/lp/soyuz/doc/archive-dependencies.txt'
--- lib/lp/soyuz/doc/archive-dependencies.txt 2009-08-28 07:34:44 +0000
+++ lib/lp/soyuz/doc/archive-dependencies.txt 2009-11-03 18:13:40 +0000
@@ -441,38 +441,27 @@
441441
442== External build dependencies ==442== External build dependencies ==
443443
444Via a static configuration change, any PPA hosted in launchpad can be444Via an administrator change, any PPA hosted in launchpad can be
445assigned to one or more 'external' build dependencies additionally to445assigned to one or more 'external' build dependencies additionally to
446the internal ones.446the internal ones.
447447
448There is a configuration category called 'ppa' which can be extended448There is a column on IArchive called 'external_dependencies' which can be set
449for any hosted PPA. They are named as following:449for any hosted PPA. It is a string listing the comma-separated external
450450dependencies in the debian sources_list format.
451 [ppa.<owner_name>_<ppa_name>]451
452452 deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] [components]
453The 'ppa' categories contain the 'dependencies' field, which is a
454multi-line string listing the external dependencies in the debian
455sources_list format.
456
457 deb [user:pass@]<host>[/path] %(series)s[-pocket] [components]
458453
459The '%(series)s' part is optional and will be replaced on-the-fly with454The '%(series)s' part is optional and will be replaced on-the-fly with
460the series name for the build record being dispatched.455the series name for the build record being dispatched.
461456
462We will create a configuration for Celso's PPA.457We will create some dependencies for Celso's PPA.
463458
464 >>> from canonical.config import config459 >>> cprov.archive.external_dependencies = (
465 >>> cprov_deps = """460 ... "deb http://user:pass@repository zoing everything\n"
466 ... [ppa.cprov_ppa]461 ... "deb http://user:pass@repository %(series)s public private\n"
467 ... dependencies:462 ... "deb http://user:pass@repository %(series)s-extra public")
468 ... deb http://user:pass@repository zoing everything463
469 ... deb http://user:pass@repository %(series)s public private464Now builds in Celso's PPA will use the external dependencies.
470 ... deb http://user:pass@repository %(series)s-extra public
471 ... """
472 >>> config.push('test_deps', cprov_deps)
473
474Configuration in place, now builds in Celso's PPA will use the
475external dependencies.
476465
477 >>> print_building_sources_list(a_build)466 >>> print_building_sources_list(a_build)
478 deb http://ftpmaster.internal/ubuntu hoary467 deb http://ftpmaster.internal/ubuntu hoary
@@ -486,6 +475,3 @@
486 deb http://user:pass@repository hoary-extra public475 deb http://user:pass@repository hoary-extra public
487 deb http://user:pass@repository zoing everything476 deb http://user:pass@repository zoing everything
488477
489Tests done, we can remove the extra configuration content we've added.
490
491 >>> unused = config.pop('test_deps')
492478
=== modified file 'lib/lp/soyuz/doc/archive.txt'
--- lib/lp/soyuz/doc/archive.txt 2009-10-13 16:12:10 +0000
+++ lib/lp/soyuz/doc/archive.txt 2009-11-03 18:13:41 +0000
@@ -58,11 +58,11 @@
58 >>> cprov_archive.failed_count58 >>> cprov_archive.failed_count
59 159 1
6060
61relative_build_score is a property that can be set only by LP admins and read61relative_build_score and external_dependencies are properties that can be set
62by anyone. It is a signed integer that represents a delta to all the build62only by LP admins and read by anyone.
63scores for builds done in the archive.
6463
65The default value is zero:64relative_build_score is a signed integer that represents a delta to all the
65build scores for builds done in the archive. The default value is zero:
6666
67 >>> cprov_archive.relative_build_score67 >>> cprov_archive.relative_build_score
68 068 0
@@ -74,10 +74,28 @@
74 ...74 ...
75 Unauthorized: (..., 'relative_build_score', 'launchpad.Commercial')75 Unauthorized: (..., 'relative_build_score', 'launchpad.Commercial')
7676
77As a Launchpad admin, it will work.77external_dependencies is a text field that should contain a comma-separated
78list of sources.list entries in the format:
79deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] [components]
80where the series variable is replaced with the series name of the context
81build. This allows an admin to set external repositories as a source for
82build dependencies on the context PPA. Its default value is None:
83
84 >>> print cprov_archive.external_dependencies
85 None
86
87Amending it as an unprivileged user results in failure:
88
89 >>> cprov_archive.external_dependencies = "test"
90 Traceback (most recent call last):
91 ...
92 Unauthorized: (..., 'external_dependencies', 'launchpad.Commercial')
93
94As a Launchpad admin, setting these properties will work.
7895
79 >>> login("admin@canonical.com")96 >>> login("admin@canonical.com")
80 >>> cprov_archive.relative_build_score = 10097 >>> cprov_archive.relative_build_score = 100
98 >>> cprov_archive.external_dependencies = "test"
8199
82The buildd_secret is used by the slave scanner when generating a100The buildd_secret is used by the slave scanner when generating a
83sources.list entry for the builder to access a private archive. It is101sources.list entry for the builder to access a private archive. It is
84102
=== modified file 'lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt'
--- lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt 2009-09-04 12:17:11 +0000
+++ lib/lp/soyuz/doc/distroarchseriesbinarypackage.txt 2009-11-03 18:13:40 +0000
@@ -209,6 +209,7 @@
209209
210 >>> ubuntu.updateCompleteSourcePackageCache(210 >>> ubuntu.updateCompleteSourcePackageCache(
211 ... archive=cprov.archive, ztm=LaunchpadZopelessLayer.txn, log=TestLog())211 ... archive=cprov.archive, ztm=LaunchpadZopelessLayer.txn, log=TestLog())
212 DEBUG: ...
212 DEBUG: Considering source 'pmount'213 DEBUG: Considering source 'pmount'
213 ...214 ...
214215
215216
=== modified file 'lib/lp/soyuz/doc/package-cache.txt'
--- lib/lp/soyuz/doc/package-cache.txt 2009-08-13 13:09:34 +0000
+++ lib/lp/soyuz/doc/package-cache.txt 2009-11-03 18:13:39 +0000
@@ -163,11 +163,12 @@
163163
164 >>> updates = ubuntu.updateCompleteSourcePackageCache(164 >>> updates = ubuntu.updateCompleteSourcePackageCache(
165 ... archive=ubuntu.main_archive, ztm=transaction, log=TestLog())165 ... archive=ubuntu.main_archive, ztm=transaction, log=TestLog())
166 DEBUG: Considering source 'mozilla-firefox'166 DEBUG: ...
167 ...
168 DEBUG: Considering source 'cdrkit'167 DEBUG: Considering source 'cdrkit'
169 DEBUG: Creating new source cache entry.168 DEBUG: Creating new source cache entry.
170 ...169 ...
170 DEBUG: Considering source 'mozilla-firefox'
171 ...
171172
172 >>> print updates173 >>> print updates
173 10174 10
@@ -355,6 +356,7 @@
355356
356 >>> source_updates = ubuntu.updateCompleteSourcePackageCache(357 >>> source_updates = ubuntu.updateCompleteSourcePackageCache(
357 ... archive=cprov.archive, ztm=transaction, log=TestLog())358 ... archive=cprov.archive, ztm=transaction, log=TestLog())
359 DEBUG: ...
358 DEBUG: Considering source 'pmount'360 DEBUG: Considering source 'pmount'
359 ...361 ...
360362
361363
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py 2009-10-26 09:43:56 +0000
+++ lib/lp/soyuz/interfaces/archive.py 2009-11-03 18:13:41 +0000
@@ -256,6 +256,18 @@
256 description=_(256 description=_(
257 "A delta to apply to all build scores for this archive."))257 "A delta to apply to all build scores for this archive."))
258258
259 external_dependencies = Text(
260 title=_("External dependencies"), required=False, readonly=False,
261 description=_(
262 "Newline-separated list of repositories to be used to retrieve "
263 "any external build dependencies when building packages in this "
264 "archive, in the format:\n"
265 "deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] "
266 "[components]\n"
267 "The series variable is replaced with the series name of the "
268 "context build.\n"
269 "NOTE: This is for migration of OEM PPAs only!"))
270
259 def getSourcesForDeletion(name=None, status=None, distroseries=None):271 def getSourcesForDeletion(name=None, status=None, distroseries=None):
260 """All `ISourcePackagePublishingHistory` available for deletion.272 """All `ISourcePackagePublishingHistory` available for deletion.
261273
@@ -433,8 +445,10 @@
433445
434 @operation_parameters(446 @operation_parameters(
435 person=Reference(schema=IPerson),447 person=Reference(schema=IPerson),
436 packageset=TextLine(448 # Really IPackageset, corrected in _schema_circular_imports to avoid
437 title=_("Package set"), required=True),449 # circular import.
450 packageset=Reference(
451 Interface, title=_("Package set"), required=True),
438 explicit=Bool(452 explicit=Bool(
439 title=_("Explicit"), required=False))453 title=_("Explicit"), required=False))
440 # Really IArchivePermission, set in _schema_circular_imports to avoid454 # Really IArchivePermission, set in _schema_circular_imports to avoid
@@ -444,7 +458,7 @@
444 """Add a package set based permission for a person.458 """Add a package set based permission for a person.
445459
446 :param person: An `IPerson` for whom you want to add permission.460 :param person: An `IPerson` for whom you want to add permission.
447 :param packageset: An `IPackageset` or a string package set name.461 :param packageset: An `IPackageset`.
448 :param explicit: True if the package set in question requires462 :param explicit: True if the package set in question requires
449 specialist skills for proper handling.463 specialist skills for proper handling.
450464
@@ -453,8 +467,10 @@
453 """467 """
454468
455 @operation_parameters(469 @operation_parameters(
456 packageset=TextLine(470 # Really IPackageset, corrected in _schema_circular_imports to avoid
457 title=_("Package set"), required=True),471 # circular import.
472 packageset=Reference(
473 Interface, title=_("Package set"), required=True),
458 direct_permissions=Bool(474 direct_permissions=Bool(
459 title=_("Ignore package set hierarchy"), required=False))475 title=_("Ignore package set hierarchy"), required=False))
460 # Really IArchivePermission, set in _schema_circular_imports to avoid476 # Really IArchivePermission, set in _schema_circular_imports to avoid
@@ -464,7 +480,7 @@
464 def getUploadersForPackageset(packageset, direct_permissions=True):480 def getUploadersForPackageset(packageset, direct_permissions=True):
465 """The `ArchivePermission` records for uploaders to the package set.481 """The `ArchivePermission` records for uploaders to the package set.
466482
467 :param packageset: An `IPackageset` or a string package set name.483 :param packageset: An `IPackageset`.
468 :param direct_permissions: If True, only consider permissions granted484 :param direct_permissions: If True, only consider permissions granted
469 directly for the package set at hand. Otherwise, include any485 directly for the package set at hand. Otherwise, include any
470 uploaders for package sets that include this one.486 uploaders for package sets that include this one.
@@ -475,8 +491,10 @@
475491
476 @operation_parameters(492 @operation_parameters(
477 person=Reference(schema=IPerson),493 person=Reference(schema=IPerson),
478 packageset=TextLine(494 # Really IPackageset, corrected in _schema_circular_imports to avoid
479 title=_("Package set"), required=True),495 # circular import.
496 packageset=Reference(
497 Interface, title=_("Package set"), required=True),
480 explicit=Bool(498 explicit=Bool(
481 title=_("Explicit"), required=False))499 title=_("Explicit"), required=False))
482 @export_write_operation()500 @export_write_operation()
@@ -484,7 +502,7 @@
484 """Revoke upload permissions for a person.502 """Revoke upload permissions for a person.
485503
486 :param person: An `IPerson` for whom you want to revoke permission.504 :param person: An `IPerson` for whom you want to revoke permission.
487 :param packageset: An `IPackageset` or a string package set name.505 :param packageset: An `IPackageset`.
488 :param explicit: The value of the 'explicit' flag for the permission506 :param explicit: The value of the 'explicit' flag for the permission
489 to be revoked.507 to be revoked.
490 """508 """
@@ -567,9 +585,13 @@
567 @operation_parameters(585 @operation_parameters(
568 sourcepackagename=TextLine(586 sourcepackagename=TextLine(
569 title=_("Source package name"), required=True),587 title=_("Source package name"), required=True),
570 person=Reference(schema=IPerson))588 person=Reference(schema=IPerson),
589 distroseries=Reference(
590 # Really IDistroSeries, avoiding a circular import here.
591 Interface,
592 title=_("The distro series"), required=False))
571 @export_read_operation()593 @export_read_operation()
572 def isSourceUploadAllowed(sourcepackagename, person):594 def isSourceUploadAllowed(sourcepackagename, person, distroseries=None):
573 """True if the person is allowed to upload the given source package.595 """True if the person is allowed to upload the given source package.
574596
575 Return True if there exists a permission that combines597 Return True if there exists a permission that combines
@@ -585,6 +607,9 @@
585 either a string or a `ISourcePackageName`.607 either a string or a `ISourcePackageName`.
586 :param person: An `IPerson` for whom you want to find out which608 :param person: An `IPerson` for whom you want to find out which
587 package sets he has access to.609 package sets he has access to.
610 :param distroseries: The `IDistroSeries` for which to check
611 permissions. If none is supplied then `currentseries` in
612 Ubuntu is assumed.
588613
589 :raises NoSuchSourcePackageName: if a source package with the614 :raises NoSuchSourcePackageName: if a source package with the
590 given name could not be found.615 given name could not be found.
591616
=== modified file 'lib/lp/soyuz/interfaces/archivepermission.py'
--- lib/lp/soyuz/interfaces/archivepermission.py 2009-08-03 17:10:12 +0000
+++ lib/lp/soyuz/interfaces/archivepermission.py 2009-11-03 18:13:41 +0000
@@ -258,7 +258,8 @@
258 archive in question.258 archive in question.
259 """259 """
260260
261 def isSourceUploadAllowed(archive, sourcepackagename, person):261 def isSourceUploadAllowed(
262 archive, sourcepackagename, person, distroseries=None):
262 """True if the person is allowed to upload the given source package.263 """True if the person is allowed to upload the given source package.
263264
264 Return True if there exists a permission that combines265 Return True if there exists a permission that combines
@@ -275,6 +276,9 @@
275 either a string or a `ISourcePackageName`.276 either a string or a `ISourcePackageName`.
276 :param person: An `IPerson` for whom you want to find out which277 :param person: An `IPerson` for whom you want to find out which
277 package sets he has access to.278 package sets he has access to.
279 :param distroseries: The `IDistroSeries` for which to check
280 permissions. If none is supplied then `currentseries` in
281 Ubuntu is assumed.
278282
279 :raises SourceNotFound: if a source package with the given283 :raises SourceNotFound: if a source package with the given
280 name could not be found.284 name could not be found.
@@ -284,6 +288,9 @@
284 def uploadersForPackageset(archive, packageset, direct_permissions=True):288 def uploadersForPackageset(archive, packageset, direct_permissions=True):
285 """The `ArchivePermission` records for uploaders to the package set.289 """The `ArchivePermission` records for uploaders to the package set.
286290
291 Please note: if a package set *name* is passed the respective
292 package set in the current distro series will be used.
293
287 :param archive: The archive the permission applies to.294 :param archive: The archive the permission applies to.
288 :param packageset: An `IPackageset` or a string package set name.295 :param packageset: An `IPackageset` or a string package set name.
289 :param direct_permissions: If True only consider permissions granted296 :param direct_permissions: If True only consider permissions granted
@@ -332,6 +339,9 @@
332 def newPackagesetUploader(archive, person, packageset, explicit=False):339 def newPackagesetUploader(archive, person, packageset, explicit=False):
333 """Create and return a new `ArchivePermission` for an uploader.340 """Create and return a new `ArchivePermission` for an uploader.
334341
342 Please note: if a package set *name* is passed the respective
343 package set in the current distro series will be used.
344
335 :param archive: The archive the permission applies to.345 :param archive: The archive the permission applies to.
336 :param person: An `IPerson` for whom you want to add permission.346 :param person: An `IPerson` for whom you want to add permission.
337 :param packageset: An `IPackageset` or a string package set name.347 :param packageset: An `IPackageset` or a string package set name.
@@ -379,6 +389,9 @@
379 def deletePackagesetUploader(archive, person, packageset, explicit=False):389 def deletePackagesetUploader(archive, person, packageset, explicit=False):
380 """Revoke upload permissions for a person.390 """Revoke upload permissions for a person.
381391
392 Please note: if a package set *name* is passed the respective
393 package set in the current distro series will be used.
394
382 :param archive: The archive the permission applies to.395 :param archive: The archive the permission applies to.
383 :param person: An `IPerson` for whom you want to revoke permission.396 :param person: An `IPerson` for whom you want to revoke permission.
384 :param packageset: An `IPackageset` or a string package set name.397 :param packageset: An `IPackageset` or a string package set name.
385398
=== modified file 'lib/lp/soyuz/interfaces/packageset.py'
--- lib/lp/soyuz/interfaces/packageset.py 2009-07-25 16:33:39 +0000
+++ lib/lp/soyuz/interfaces/packageset.py 2009-11-03 18:13:41 +0000
@@ -8,6 +8,7 @@
8__metaclass__ = type8__metaclass__ = type
99
10__all__ = [10__all__ = [
11 'DuplicatePackagesetName',
11 'IPackageset',12 'IPackageset',
12 'IPackagesetSet',13 'IPackagesetSet',
13 'NoSuchPackageSet',14 'NoSuchPackageSet',
@@ -26,14 +27,23 @@
26 operation_parameters, operation_returns_collection_of,27 operation_parameters, operation_returns_collection_of,
27 operation_returns_entry, webservice_error)28 operation_returns_entry, webservice_error)
28from lazr.restful.fields import Reference29from lazr.restful.fields import Reference
30from lp.registry.interfaces.distroseries import IDistroSeries
29from lp.registry.interfaces.person import IPerson31from lp.registry.interfaces.person import IPerson
30from lp.registry.interfaces.role import IHasOwner32from lp.registry.interfaces.role import IHasOwner
33from lp.soyuz.interfaces.packagesetgroup import IPackagesetGroup
3134
3235
33class NoSuchPackageSet(NameLookupFailed):36class NoSuchPackageSet(NameLookupFailed):
34 """Raised when we try to look up an PackageSet that doesn't exist."""37 """Raised when we try to look up an PackageSet that doesn't exist."""
35 webservice_error(400) #Bad request.38 # Bad request.
36 _message_prefix = "No such packageset"39 webservice_error(400)
40 _message_prefix = "No such package set (in the specified distro series)"
41
42
43class DuplicatePackagesetName(Exception):
44 """Raised for packagesets with the same name and distroseries."""
45 # Bad request.
46 webservice_error(400)
3747
3848
39class IPackagesetViewOnly(IHasOwner):49class IPackagesetViewOnly(IHasOwner):
@@ -48,7 +58,7 @@
4858
49 owner = exported(Reference(59 owner = exported(Reference(
50 IPerson, title=_("Person"), required=True, readonly=True,60 IPerson, title=_("Person"), required=True, readonly=True,
51 description=_("The person who owns the package set at hand.")))61 description=_("The person who owns this package set.")))
5262
53 name = exported(TextLine(63 name = exported(TextLine(
54 title=_('Valid package set name'),64 title=_('Valid package set name'),
@@ -58,6 +68,18 @@
58 title=_("Description"), required=True, readonly=True,68 title=_("Description"), required=True, readonly=True,
59 description=_("The description for the package set at hand.")))69 description=_("The description for the package set at hand.")))
6070
71 distroseries = exported(Reference(
72 IDistroSeries, title=_("Distribution series"), required=True,
73 readonly=True,
74 description=_(
75 "The distroseries to which this package set is related.")))
76
77 packagesetgroup = Reference(
78 IPackagesetGroup, title=_('Package set group'), required=True,
79 readonly=True,
80 description=_(
81 'Used internally to link package sets across distro series.'))
82
61 def sourcesIncluded(direct_inclusion=False):83 def sourcesIncluded(direct_inclusion=False):
62 """Get all source names associated with this package set.84 """Get all source names associated with this package set.
6385
@@ -195,6 +217,16 @@
195 names.217 names.
196 """218 """
197219
220 @operation_returns_collection_of(Interface)
221 @export_read_operation()
222 def relatedSets():
223 """Get all package sets related to this one.
224
225 Return all package sets that are related to this one.
226
227 :return: A (potentially empty) sequence of `IPackageset` instances.
228 """
229
198230
199class IPackagesetEdit(Interface):231class IPackagesetEdit(Interface):
200 """A writeable interface for package sets."""232 """A writeable interface for package sets."""
@@ -316,15 +348,31 @@
316 title=_('Package set description'), required=True),348 title=_('Package set description'), required=True),
317 owner=Reference(349 owner=Reference(
318 IPerson, title=_("Person"), required=True, readonly=True,350 IPerson, title=_("Person"), required=True, readonly=True,
319 description=_("The person who owns the package set at hand.")))351 description=_("The person who owns this package set.")),
352 distroseries=Reference(
353 IDistroSeries, title=_("Distroseries"), required=False,
354 readonly=True, description=_(
355 "The distribution series to which the packageset "
356 "is related.")),
357 related_set=Reference(
358 IPackageset, title=_("Related package set"), required=False,
359 readonly=True, description=_(
360 "The new package set will share the package set group "
361 "with this one.")))
320 @export_factory_operation(IPackageset, [])362 @export_factory_operation(IPackageset, [])
321 def new(name, description, owner):363 def new(name, description, owner, distroseries=None, related_set=None):
322 """Create a new package set.364 """Create a new package set.
323365
324 :param name: the name of the package set to be created.366 :param name: the name of the package set to be created.
325 :param description: the description for the package set to be created.367 :param description: the description for the package set to be created.
326 :param owner: the owner of the package set to be created.368 :param owner: the owner of the package set to be created.
369 :param distroseries: the distroseries to which the new packageset
370 is related. Defaults to the current Ubuntu series.
371 :param related_set: the newly created package set is to be related to
372 `related_set` (by being placed in the same package group).
327373
374 :raises DuplicatePackagesetName: if a package set with the same `name`
375 exists in `distroseries` already.
328 :return: a newly created `IPackageset`.376 :return: a newly created `IPackageset`.
329 """377 """
330378
@@ -332,10 +380,12 @@
332 name=TextLine(title=_('Package set name'), required=True))380 name=TextLine(title=_('Package set name'), required=True))
333 @operation_returns_entry(IPackageset)381 @operation_returns_entry(IPackageset)
334 @export_read_operation()382 @export_read_operation()
335 def getByName(name):383 def getByName(name, distroseries=None):
336 """Return the single package set with the given name (if any).384 """Return the single package set with the given name (if any).
337385
338 :param name: the name of the package set sought.386 :param name: the name of the package set sought.
387 :param distroseries: the distroseries to which the new packageset
388 is related. Defaults to the current Ubuntu series.
339389
340 :return: An `IPackageset` instance or None.390 :return: An `IPackageset` instance or None.
341 """391 """
342392
=== added file 'lib/lp/soyuz/interfaces/packagesetgroup.py'
--- lib/lp/soyuz/interfaces/packagesetgroup.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/interfaces/packagesetgroup.py 2009-11-03 18:13:43 +0000
@@ -0,0 +1,41 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Packageset Group interface."""
5
6__metaclass__ = type
7
8__all__ = [
9 'IPackagesetGroup',
10 ]
11
12from zope.schema import Datetime, Int
13
14from lazr.restful.fields import Reference
15
16from canonical.launchpad import _
17from lp.registry.interfaces.person import IPerson
18from lp.registry.interfaces.role import IHasOwner
19
20
21class IPackagesetGroup(IHasOwner):
22 """A group of related package sets across distroseries'
23
24 This class is used internally to group related packagesets across
25 distroseries. For example, if in Karmic there is a 'gnome-games'
26 package set, and this package set is cloned initially for Lucid,
27 then both packagesets would refer to the same packageset-group.
28
29 Packageset-groups are not exposed at all. The date_created and
30 owner fields are present for internal use only.
31 """
32 id = Int(title=_('ID'), required=True, readonly=True)
33
34 date_created = Datetime(
35 title=_("Date Created"), required=True, readonly=True,
36 description=_("The creation date/time for this packageset group."))
37
38 owner = Reference(
39 IPerson, title=_("Person"), required=True, readonly=True,
40 description=_("The person who created this packageset group."))
41
042
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2009-10-26 09:43:56 +0000
+++ lib/lp/soyuz/model/archive.py 2009-11-03 18:13:42 +0000
@@ -174,6 +174,12 @@
174 relative_build_score = IntCol(174 relative_build_score = IntCol(
175 dbName='relative_build_score', notNull=True, default=0)175 dbName='relative_build_score', notNull=True, default=0)
176176
177 # This field is specifically and only intended for OEM migration to
178 # Launchpad and should be re-examined in October 2010 to see if it
179 # is still relevant.
180 external_dependencies = StringCol(
181 dbName='external_dependencies', notNull=False, default=None)
182
177 def _init(self, *args, **kw):183 def _init(self, *args, **kw):
178 """Provide the right interface for URL traversal."""184 """Provide the right interface for URL traversal."""
179 SQLBase._init(self, *args, **kw)185 SQLBase._init(self, *args, **kw)
@@ -998,11 +1004,12 @@
998 return permission_set.packagesetsForSource(1004 return permission_set.packagesetsForSource(
999 self, sourcepackagename, direct_permissions)1005 self, sourcepackagename, direct_permissions)
10001006
1001 def isSourceUploadAllowed(self, sourcepackagename, person):1007 def isSourceUploadAllowed(
1008 self, sourcepackagename, person, distroseries=None):
1002 """See `IArchive`."""1009 """See `IArchive`."""
1003 permission_set = getUtility(IArchivePermissionSet)1010 permission_set = getUtility(IArchivePermissionSet)
1004 return permission_set.isSourceUploadAllowed(1011 return permission_set.isSourceUploadAllowed(
1005 self, sourcepackagename, person)1012 self, sourcepackagename, person, distroseries)
10061013
1007 def getFileByName(self, filename):1014 def getFileByName(self, filename):
1008 """See `IArchive`."""1015 """See `IArchive`."""
10091016
=== modified file 'lib/lp/soyuz/model/archivepermission.py'
--- lib/lp/soyuz/model/archivepermission.py 2009-07-28 21:52:56 +0000
+++ lib/lp/soyuz/model/archivepermission.py 2009-11-03 18:13:43 +0000
@@ -22,6 +22,7 @@
22from canonical.database.enumcol import EnumCol22from canonical.database.enumcol import EnumCol
23from canonical.database.sqlbase import sqlvalues, SQLBase23from canonical.database.sqlbase import sqlvalues, SQLBase
2424
25from lp.registry.interfaces.distribution import IDistributionSet
25from lp.soyuz.interfaces.archive import ComponentNotFound26from lp.soyuz.interfaces.archive import ComponentNotFound
26from lp.soyuz.interfaces.archivepermission import (27from lp.soyuz.interfaces.archivepermission import (
27 ArchivePermissionType, IArchivePermission, IArchivePermissionSet,28 ArchivePermissionType, IArchivePermission, IArchivePermissionSet,
@@ -312,9 +313,13 @@
312 def _nameToPackageset(self, packageset):313 def _nameToPackageset(self, packageset):
313 """Helper to convert a possible string name to IPackageset."""314 """Helper to convert a possible string name to IPackageset."""
314 if isinstance(packageset, basestring):315 if isinstance(packageset, basestring):
316 # A package set name was passed, assume the current distro series.
317 ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
315 name = packageset318 name = packageset
316 store = IStore(Packageset)319 store = IStore(Packageset)
317 packageset = store.find(Packageset, name=name).one()320 packageset = store.find(
321 Packageset, name=name,
322 distroseries=ubuntu.currentseries).one()
318 if packageset is not None:323 if packageset is not None:
319 return packageset324 return packageset
320 else:325 else:
@@ -475,48 +480,56 @@
475 ''', (sourcepackagename.id, archive.id)))480 ''', (sourcepackagename.id, archive.id)))
476 return rset481 return rset
477482
478 def isSourceUploadAllowed(self, archive, sourcepackagename, person):483 def isSourceUploadAllowed(
484 self, archive, sourcepackagename, person, distroseries=None):
479 """See `IArchivePermissionSet`."""485 """See `IArchivePermissionSet`."""
480 sourcepackagename = self._nameToSourcePackageName(sourcepackagename)486 sourcepackagename = self._nameToSourcePackageName(sourcepackagename)
481 store = IStore(ArchivePermission)487 store = IStore(ArchivePermission)
488 if distroseries is None:
489 ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
490 distroseries = ubuntu.currentseries
482491
483 # Put together the parameters for the query that follows.492 # Put together the parameters for the query that follows.
484 archive_params = (ArchivePermissionType.UPLOAD, archive.id)493 archive_params = (ArchivePermissionType.UPLOAD, archive.id)
494 permission_params = (sourcepackagename.id, person.id, distroseries.id)
485 query_params = (495 query_params = (
486 # Query parameters for the first WHERE clause.496 # Query parameters for the first WHERE clause.
487 (archive.id, sourcepackagename.id) +497 (archive.id, distroseries.id, sourcepackagename.id) +
488 # Query parameters for the second WHERE clause.498 # Query parameters for the second WHERE clause.
489 (sourcepackagename.id,) + (person.id,) + archive_params + 499 permission_params + archive_params +
490 # Query parameters for the third WHERE clause.500 # Query parameters for the third WHERE clause.
491 (sourcepackagename.id,) + (person.id,) + archive_params)501 permission_params + archive_params)
492502
493 query = '''503 query = '''
494 SELECT CASE504 SELECT CASE
495 WHEN (505 WHEN (
496 SELECT COUNT(ap.id)506 SELECT COUNT(ap.id)
497 FROM packagesetsources pss, archivepermission ap507 FROM packagesetsources pss, archivepermission ap, packageset ps
498 WHERE508 WHERE
499 ap.archive = %s AND ap.explicit = TRUE509 ap.archive = %s AND ap.explicit = TRUE
510 AND ap.packageset = ps.id AND ps.distroseries = %s
500 AND pss.sourcepackagename = %s511 AND pss.sourcepackagename = %s
501 AND pss.packageset = ap.packageset) > 0512 AND pss.packageset = ap.packageset) > 0
502 THEN (513 THEN (
503 SELECT COUNT(ap.id)514 SELECT COUNT(ap.id)
504 FROM515 FROM
505 packagesetsources pss, archivepermission ap,516 packagesetsources pss, archivepermission ap, packageset ps,
506 teamparticipation tp517 teamparticipation tp
507 WHERE518 WHERE
508 pss.sourcepackagename = %s519 pss.sourcepackagename = %s
509 AND ap.person = tp.team AND tp.person = %s520 AND ap.person = tp.team AND tp.person = %s
521 AND ap.packageset = ps.id AND ps.distroseries = %s
510 AND pss.packageset = ap.packageset AND ap.explicit = TRUE522 AND pss.packageset = ap.packageset AND ap.explicit = TRUE
511 AND ap.permission = %s AND ap.archive = %s)523 AND ap.permission = %s AND ap.archive = %s)
512 ELSE (524 ELSE (
513 SELECT COUNT(ap.id)525 SELECT COUNT(ap.id)
514 FROM526 FROM
515 packagesetsources pss, archivepermission ap,527 packagesetsources pss, archivepermission ap, packageset ps,
516 teamparticipation tp, flatpackagesetinclusion fpsi528 teamparticipation tp, flatpackagesetinclusion fpsi
517 WHERE529 WHERE
518 pss.sourcepackagename = %s530 pss.sourcepackagename = %s
519 AND ap.person = tp.team AND tp.person = %s531 AND ap.person = tp.team AND tp.person = %s
532 AND ap.packageset = ps.id AND ps.distroseries = %s
520 AND pss.packageset = fpsi.child AND fpsi.parent = ap.packageset533 AND pss.packageset = fpsi.child AND fpsi.parent = ap.packageset
521 AND ap.permission = %s AND ap.archive = %s)534 AND ap.permission = %s AND ap.archive = %s)
522 END AS number_of_permitted_package_sets;535 END AS number_of_permitted_package_sets;
523536
=== modified file 'lib/lp/soyuz/model/packageset.py'
--- lib/lp/soyuz/model/packageset.py 2009-07-25 16:33:39 +0000
+++ lib/lp/soyuz/model/packageset.py 2009-11-03 18:13:42 +0000
@@ -6,6 +6,7 @@
66
7import pytz7import pytz
88
9from storm.exceptions import IntegrityError
9from storm.expr import In, SQL10from storm.expr import In, SQL
10from storm.locals import DateTime, Int, Reference, Storm, Unicode11from storm.locals import DateTime, Int, Reference, Storm, Unicode
1112
@@ -13,12 +14,14 @@
13from zope.interface import implements14from zope.interface import implements
1415
15from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore16from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore
17from canonical.launchpad.webapp.interfaces import NotFoundError
18from lp.registry.interfaces.distribution import IDistributionSet
16from lp.registry.interfaces.sourcepackagename import (19from lp.registry.interfaces.sourcepackagename import (
17 ISourcePackageName, ISourcePackageNameSet)20 ISourcePackageName, ISourcePackageNameSet)
18from lp.registry.model.sourcepackagename import SourcePackageName21from lp.registry.model.sourcepackagename import SourcePackageName
19from lp.soyuz.interfaces.packageset import (22from lp.soyuz.interfaces.packageset import (
20 IPackageset, IPackagesetSet, NoSuchPackageSet)23 DuplicatePackagesetName, IPackageset, IPackagesetSet, NoSuchPackageSet)
2124from lp.soyuz.model.packagesetgroup import PackagesetGroup
2225
23def _order_result_set(result_set):26def _order_result_set(result_set):
24 """Default order for package set and source package name result sets."""27 """Default order for package set and source package name result sets."""
@@ -45,6 +48,12 @@
45 name = Unicode(name='name', allow_none=False)48 name = Unicode(name='name', allow_none=False)
46 description = Unicode(name='description', allow_none=False)49 description = Unicode(name='description', allow_none=False)
4750
51 distroseries_id = Int(name='distroseries', allow_none=False)
52 distroseries = Reference(distroseries_id, 'DistroSeries.id')
53
54 packagesetgroup_id = Int(name='packagesetgroup', allow_none=False)
55 packagesetgroup = Reference(packagesetgroup_id, 'PackagesetGroup.id')
56
48 def add(self, data):57 def add(self, data):
49 """See `IPackageset`."""58 """See `IPackageset`."""
50 handlers = (59 handlers = (
@@ -279,41 +288,101 @@
279288
280 def addSubsets(self, names):289 def addSubsets(self, names):
281 """See `IPackageset`."""290 """See `IPackageset`."""
282 clauses = (Packageset, In(Packageset.name, names))291 clauses = (
292 Packageset, In(Packageset.name, names),
293 Packageset.distroseries == self.distroseries)
283 self._api_add_or_remove(clauses, self._addDirectSuccessors)294 self._api_add_or_remove(clauses, self._addDirectSuccessors)
284295
285 def removeSubsets(self, names):296 def removeSubsets(self, names):
286 """See `IPackageset`."""297 """See `IPackageset`."""
287 clauses = (Packageset, In(Packageset.name, names))298 clauses = (
299 Packageset, In(Packageset.name, names),
300 Packageset.distroseries == self.distroseries)
288 self._api_add_or_remove(clauses, self._removeDirectSuccessors)301 self._api_add_or_remove(clauses, self._removeDirectSuccessors)
289302
303 def relatedSets(self):
304 """See `IPackageset`."""
305 store = IStore(Packageset)
306 result_set = store.find(
307 Packageset,
308 Packageset.packagesetgroup == self.packagesetgroup,
309 Packageset.id != self.id)
310 return _order_result_set(result_set)
311
290312
291class PackagesetSet:313class PackagesetSet:
292 """See `IPackagesetSet`."""314 """See `IPackagesetSet`."""
293 implements(IPackagesetSet)315 implements(IPackagesetSet)
294316
295 def new(self, name, description, owner):317 def new(
318 self, name, description, owner, distroseries=None, related_set=None):
296 """See `IPackagesetSet`."""319 """See `IPackagesetSet`."""
297 store = IMasterStore(Packageset)320 store = IMasterStore(Packageset)
321
322 packagesetgroup = None
323 if related_set is not None:
324 # Use the packagesetgroup of the `related_set`.
325 packagesetgroup = related_set.packagesetgroup
326 else:
327 # We create the related internal PackagesetGroup for this
328 # packageset so that we can later see related package sets across
329 # distroserieses.
330 packagesetgroup = PackagesetGroup()
331 packagesetgroup.owner = owner
332 store.add(packagesetgroup)
333
334 if distroseries is None:
335 ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
336 distroseries = ubuntu.currentseries
337
298 packageset = Packageset()338 packageset = Packageset()
339 packageset.packagesetgroup = packagesetgroup
299 packageset.name = name340 packageset.name = name
300 packageset.description = description341 packageset.description = description
301 packageset.owner = owner342 packageset.owner = owner
343
344 packageset.distroseries = distroseries
345
302 store.add(packageset)346 store.add(packageset)
347
348 # We need to ensure that the cached statements are flushed so that
349 # the duplicate name constraint gets triggered here.
350 try:
351 store.flush()
352 except IntegrityError:
353 raise DuplicatePackagesetName()
354
303 return packageset355 return packageset
304356
305 def __getitem__(self, name):357 def __getitem__(self, name):
306 """See `IPackagesetSet`."""358 """See `IPackagesetSet`."""
307 return self.getByName(name)359 return self.getByName(name)
308360
309 def getByName(self, name):361 def getByName(self, name, distroseries=None):
310 """See `IPackagesetSet`."""362 """See `IPackagesetSet`."""
311 store = IStore(Packageset)363 store = IStore(Packageset)
312 if not isinstance(name, unicode):364 if not isinstance(name, unicode):
313 name = unicode(name, 'utf-8')365 name = unicode(name, 'utf-8')
314 package_set = store.find(Packageset, Packageset.name == name).one()366
367 ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
368 extra_args = []
369 if distroseries is not None:
370 # If the user just passed a distro series name, look it up.
371 if isinstance(distroseries, basestring):
372 try:
373 distroseries = ubuntu[distroseries]
374 except NotFoundError:
375 raise NoSuchPackageSet(distroseries)
376 extra_args.append(Packageset.distroseries == distroseries)
377 else:
378 extra_args.append(Packageset.distroseries == ubuntu.currentseries)
379
380 package_set = store.find(
381 Packageset, Packageset.name == name, *extra_args).one()
382
315 if package_set is None:383 if package_set is None:
316 raise NoSuchPackageSet(name)384 raise NoSuchPackageSet(name)
385
317 return package_set386 return package_set
318387
319 def getByOwner(self, owner):388 def getByOwner(self, owner):
320389
=== added file 'lib/lp/soyuz/model/packagesetgroup.py'
--- lib/lp/soyuz/model/packagesetgroup.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/model/packagesetgroup.py 2009-11-03 18:13:41 +0000
@@ -0,0 +1,30 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4__metaclass__ = type
5
6__all__ = [
7 'PackagesetGroup',
8 ]
9
10import pytz
11
12from storm.locals import DateTime, Int, Reference, Storm
13
14from zope.interface import implements
15
16from lp.soyuz.interfaces.packagesetgroup import IPackagesetGroup
17
18
19class PackagesetGroup(Storm):
20 """See `IPackageset`."""
21 implements(IPackagesetGroup)
22 __storm_table__ = 'PackagesetGroup'
23 id = Int(primary=True)
24
25 date_created = DateTime(
26 name='date_created', allow_none=False, tzinfo=pytz.UTC)
27
28 owner_id = Int(name='owner', allow_none=False)
29 owner = Reference(owner_id, 'Person.id')
30
031
=== modified file 'lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt'
--- lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt 2009-10-13 10:05:58 +0000
+++ lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt 2009-11-03 18:13:40 +0000
@@ -354,6 +354,7 @@
354 * Set a maximum disk size: uploads will be rejected if the resulting354 * Set a maximum disk size: uploads will be rejected if the resulting
355 PPA size is exceeding the authorized size.355 PPA size is exceeding the authorized size.
356 * Set a per-archive build score delta.356 * Set a per-archive build score delta.
357 * Set external archive dependencies
357358
358In this case, the administrator may wish to amend the PPA so that it is359In this case, the administrator may wish to amend the PPA so that it is
359set up like the ubuntu security PPA, which is private but does not360set up like the ubuntu security PPA, which is private but does not
@@ -367,6 +368,8 @@
367 True368 True
368 >>> admin_browser.getControl(name="field.relative_build_score").value369 >>> admin_browser.getControl(name="field.relative_build_score").value
369 '0'370 '0'
371 >>> admin_browser.getControl(name="field.external_dependencies").value
372 ''
370373
371 >>> admin_browser.getControl(name="field.enabled").value = False374 >>> admin_browser.getControl(name="field.enabled").value = False
372 >>> admin_browser.getControl(name="field.private").value = True375 >>> admin_browser.getControl(name="field.private").value = True
@@ -376,6 +379,9 @@
376 >>> admin_browser.getControl(name="field.authorized_size").value = '1'379 >>> admin_browser.getControl(name="field.authorized_size").value = '1'
377 >>> admin_browser.getControl(380 >>> admin_browser.getControl(
378 ... name="field.relative_build_score").value = '199'381 ... name="field.relative_build_score").value = '199'
382 >>> admin_browser.getControl(
383 ... name="field.external_dependencies"
384 ... ).value = "deb http://my.spethial.repo.com/ %(series)s main"
379 >>> admin_browser.getControl("Save").click()385 >>> admin_browser.getControl("Save").click()
380386
381Once confirmed the administrator is sent to the PPA page where he can387Once confirmed the administrator is sent to the PPA page where he can
@@ -388,16 +394,33 @@
388 ... print msg394 ... print msg
389 This archive has been disabled.395 This archive has been disabled.
390396
391We need go back to the "Administer archive" page to see the build score change397We need go back to the "Administer archive" page to see the build score and
392that was made:398external dependencies changes that were made:
393399
394 >>> admin_browser.getLink("Administer archive").click()400 >>> admin_browser.getLink("Administer archive").click()
395 >>> admin_browser.getControl(name="field.relative_build_score").value401 >>> admin_browser.getControl(name="field.relative_build_score").value
396 '199'402 '199'
403 >>> admin_browser.getControl(name="field.external_dependencies").value
404 'deb http://my.spethial.repo.com/ %(series)s main'
405
406The external dependencies field is validated to make sure it looks like
407a sources.list entry. If the field fails validation an error is displayed.
408
409 >>> admin_browser.getControl(
410 ... name="field.external_dependencies"
411 ... ).value = "deb not_a_url"
412 >>> admin_browser.getControl("Save").click()
413 >>> for error in get_feedback_messages(admin_browser.contents):
414 ... print error
415 There is 1 error.
416 'deb not_a_url' is not a complete and valid sources.list entry
417
397418
398When the archive is private, the buildd secret must also be set, or an419When the archive is private, the buildd secret must also be set, or an
399error is issued:420error is issued:
400421
422 >>> admin_browser.getControl(
423 ... name="field.external_dependencies").value = ""
401 >>> admin_browser.getControl(name="field.private").value = True424 >>> admin_browser.getControl(name="field.private").value = True
402 >>> admin_browser.getControl(name="field.buildd_secret").value = ""425 >>> admin_browser.getControl(name="field.buildd_secret").value = ""
403 >>> admin_browser.getControl("Save").click()426 >>> admin_browser.getControl("Save").click()
404427
=== modified file 'lib/lp/soyuz/stories/webservice/xx-packageset.txt'
--- lib/lp/soyuz/stories/webservice/xx-packageset.txt 2009-08-20 04:46:48 +0000
+++ lib/lp/soyuz/stories/webservice/xx-packageset.txt 2009-11-03 18:13:41 +0000
@@ -18,6 +18,9 @@
18Please refer to the tests contained in the file above if you are really18Please refer to the tests contained in the file above if you are really
19interested in package sets and the complete functionality they offer.19interested in package sets and the complete functionality they offer.
2020
21
22== General package set properties ==
23
21We start off by creating an 'umbrella' package set that will include all24We start off by creating an 'umbrella' package set that will include all
22source packages.25source packages.
2326
@@ -52,16 +55,16 @@
52Can we access it via the webservice API as well?55Can we access it via the webservice API as well?
5356
54 >>> logout()57 >>> logout()
55 >>> umbrella = webservice.get("/package-sets/umbrella").jsonBody()58 >>> umbrella = webservice.get("/package-sets/hoary/umbrella").jsonBody()
56 >>> print umbrella['self_link']59 >>> print umbrella['self_link']
57 http://api.launchpad.dev/beta/package-sets/umbrella60 http://api.launchpad.dev/beta/package-sets/hoary/umbrella
5861
59`PackageSet`s can be looked up by name.62`PackageSet`s can be looked up by name.
6063
61 >>> response = webservice.named_get(64 >>> response = webservice.named_get(
62 ... '/package-sets', 'getByName', {}, name=u'umbrella')65 ... '/package-sets', 'getByName', {}, name=u'umbrella')
63 >>> print response.jsonBody()['self_link']66 >>> print response.jsonBody()['self_link']
64 http://api.launchpad.dev/beta/package-sets/umbrella67 http://api.launchpad.dev/beta/package-sets/hoary/umbrella
6568
66When a `PackageSet` cannot be found, an error is returned.69When a `PackageSet` cannot be found, an error is returned.
6770
@@ -70,9 +73,17 @@
70 >>> print response73 >>> print response
71 HTTP/1.1 400 Bad Request74 HTTP/1.1 400 Bad Request
72 ...75 ...
73 NoSuchPackageSet: No such packageset: 'not-found'.76 No such package set (in the specified distro series): 'not-found'.
77 ...
74 <BLANKLINE>78 <BLANKLINE>
7579
80Here's an example with a funny URL concoted by a "smart" user.
81
82 >>> response = webservice.get("/package-sets/lucid-plus-1/umbrella/+pwn")
83 >>> print response
84 HTTP/1.1 404 Not Found
85 ...
86
76Populate the 'umbrella' package set with source packages.87Populate the 'umbrella' package set with source packages.
7788
78 >>> from canonical.launchpad.webapp.interfaces import (89 >>> from canonical.launchpad.webapp.interfaces import (
@@ -81,7 +92,7 @@
81 >>> store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)92 >>> store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
82 >>> all_spns = store.find(SourcePackageName)93 >>> all_spns = store.find(SourcePackageName)
83 >>> response = webservice.named_post(94 >>> response = webservice.named_post(
84 ... '/package-sets/umbrella', 'addSources', {},95 ... '/package-sets/hoary/umbrella', 'addSources', {},
85 ... names=[spn.name for spn in all_spns])96 ... names=[spn.name for spn in all_spns])
86 >>> print response97 >>> print response
87 HTTP/1.1 200 Ok98 HTTP/1.1 200 Ok
@@ -91,7 +102,7 @@
91exist will not fail. Non-existing source package names are *ignored*.102exist will not fail. Non-existing source package names are *ignored*.
92103
93 >>> response = webservice.named_post(104 >>> response = webservice.named_post(
94 ... '/package-sets/umbrella', 'addSources', {},105 ... '/package-sets/hoary/umbrella', 'addSources', {},
95 ... names=[u'does-not-exist'])106 ... names=[u'does-not-exist'])
96 >>> print response107 >>> print response
97 HTTP/1.1 200 Ok108 HTTP/1.1 200 Ok
@@ -99,7 +110,7 @@
99 null110 null
100111
101 >>> response = webservice.named_post(112 >>> response = webservice.named_post(
102 ... '/package-sets/umbrella', 'removeSources', {},113 ... '/package-sets/hoary/umbrella', 'removeSources', {},
103 ... names=[u'does-not-exist'])114 ... names=[u'does-not-exist'])
104 >>> print response115 >>> print response
105 HTTP/1.1 200 Ok116 HTTP/1.1 200 Ok
@@ -109,7 +120,7 @@
109Let's see what we got.120Let's see what we got.
110121
111 >>> response = webservice.named_get(122 >>> response = webservice.named_get(
112 ... '/package-sets/umbrella', 'getSourcesIncluded', {})123 ... '/package-sets/hoary/umbrella', 'getSourcesIncluded', {})
113 >>> print response124 >>> print response
114 HTTP/1.1 200 Ok125 HTTP/1.1 200 Ok
115 ...126 ...
@@ -136,7 +147,7 @@
136from the 'umbrella' package set.147from the 'umbrella' package set.
137148
138 >>> response = webservice.named_post(149 >>> response = webservice.named_post(
139 ... '/package-sets/umbrella', 'removeSources', {},150 ... '/package-sets/hoary/umbrella', 'removeSources', {},
140 ... names=["foobar", "iceweasel"])151 ... names=["foobar", "iceweasel"])
141 >>> print response152 >>> print response
142 HTTP/1.1 200 Ok153 HTTP/1.1 200 Ok
@@ -146,7 +157,7 @@
146from the list below.157from the list below.
147158
148 >>> response = webservice.named_get(159 >>> response = webservice.named_get(
149 ... '/package-sets/umbrella', 'getSourcesIncluded', {})160 ... '/package-sets/hoary/umbrella', 'getSourcesIncluded', {})
150 >>> print response161 >>> print response
151 HTTP/1.1 200 Ok162 HTTP/1.1 200 Ok
152 ...163 ...
@@ -176,13 +187,13 @@
176187
177 >>> response = webservice.get("/package-sets/")188 >>> response = webservice.get("/package-sets/")
178 >>> print_payload(response)189 >>> print_payload(response)
179 http://api.launchpad.dev/beta/package-sets/umbrella190 http://api.launchpad.dev/beta/package-sets/hoary/umbrella
180191
181Package sets may include other package sets (as subsets). At this point,192Package sets may include other package sets (as subsets). At this point,
182however, we only have the 'umbrella' package set. It hence has no subsets.193however, we only have the 'umbrella' package set. It hence has no subsets.
183194
184 >>> response = webservice.named_get(195 >>> response = webservice.named_get(
185 ... '/package-sets/umbrella', 'setsIncluded', {})196 ... '/package-sets/hoary/umbrella', 'setsIncluded', {})
186 >>> print response197 >>> print response
187 HTTP/1.1 200 Ok198 HTTP/1.1 200 Ok
188 ...199 ...
@@ -206,6 +217,50 @@
206 HTTP/1.1 201 Created217 HTTP/1.1 201 Created
207 ...218 ...
208219
220
221=== Package sets and distro series ===
222
223Every package set is associated with a distro series.
224
225 >>> from lazr.restful.testing.webservice import pprint_entry
226 >>> mozilla = webservice.named_get(
227 ... '/package-sets', 'getByName', {}, name=u'mozilla').jsonBody()
228 >>> print mozilla['distroseries_link']
229 http://api.launchpad.dev/beta/ubuntu/hoary
230
231
232=== Related package sets ===
233
234When adding a package set we can specify that is to be related to another set
235that exists already.
236
237 >>> grumpy = webservice.get("/ubuntu/grumpy").jsonBody()
238 >>> print grumpy['self_link']
239 http://api.launchpad.dev/beta/ubuntu/grumpy
240
241We are adding a new 'mozilla' package set to the 'grumpy' distro series and
242it is related to 'mozilla' in 'hoary'.
243
244 >>> response = webservice.named_post(
245 ... '/package-sets', 'new', {},
246 ... name=u'mozilla',
247 ... description=u'Contains all mozilla packages in grumpy',
248 ... owner=name12['self_link'], distroseries=grumpy['self_link'],
249 ... related_set=mozilla['self_link'])
250 >>> print response
251 HTTP/1.1 201 Created
252 ...
253
254 >>> response = webservice.named_get(
255 ... mozilla['self_link'], 'relatedSets', {})
256 >>> print_payload(response)
257 http://api.launchpad.dev/beta/package-sets/grumpy/mozilla
258
259
260== Package set hierarchy ==
261
262More package sets are needed to set up the hierarchy described below.
263
209 >>> response = webservice.named_post(264 >>> response = webservice.named_post(
210 ... '/package-sets', 'new', {},265 ... '/package-sets', 'new', {},
211 ... name=u'firefox', description=u'Contains all firefox packages',266 ... name=u'firefox', description=u'Contains all firefox packages',
@@ -232,6 +287,18 @@
232 HTTP/1.1 201 Created287 HTTP/1.1 201 Created
233 ...288 ...
234289
290The 'languagepack' package set will be removed later (in hoary). Let's add a
291set with the same name in 'grumpy' to make sure that the right one is found.
292
293 >>> response = webservice.named_post(
294 ... '/package-sets', 'new', {},
295 ... name=u'languagepack',
296 ... description=u'Contains all languagepack packages',
297 ... owner=name12['self_link'], distroseries=grumpy['self_link'])
298 >>> print response
299 HTTP/1.1 201 Created
300 ...
301
235In order to test whether methods relating to package set hierarchies were302In order to test whether methods relating to package set hierarchies were
236exposed on the Launchpad API correctly we will define the following package303exposed on the Launchpad API correctly we will define the following package
237set hierarchy:304set hierarchy:
@@ -245,27 +312,27 @@
245 * languagepack312 * languagepack
246313
247 >>> response = webservice.named_post(314 >>> response = webservice.named_post(
248 ... '/package-sets/umbrella', 'addSubsets', {},315 ... '/package-sets/hoary/umbrella', 'addSubsets', {},
249 ... names=[u'gnome', u'mozilla'])316 ... names=[u'gnome', u'mozilla'])
250 >>> print response317 >>> print response
251 HTTP/1.1 200 Ok318 HTTP/1.1 200 Ok
252 ...319 ...
253320
254 >>> response = webservice.named_post(321 >>> response = webservice.named_post(
255 ... '/package-sets/gnome', 'addSubsets', {}, names=[u'languagepack'])322 ... '/package-sets/hoary/gnome', 'addSubsets', {}, names=[u'languagepack'])
256 >>> print response323 >>> print response
257 HTTP/1.1 200 Ok324 HTTP/1.1 200 Ok
258 ...325 ...
259326
260 >>> response = webservice.named_post(327 >>> response = webservice.named_post(
261 ... '/package-sets/thunderbird', 'addSubsets', {},328 ... '/package-sets/hoary/thunderbird', 'addSubsets', {},
262 ... names=[u'languagepack'])329 ... names=[u'languagepack'])
263 >>> print response330 >>> print response
264 HTTP/1.1 200 Ok331 HTTP/1.1 200 Ok
265 ...332 ...
266333
267 >>> response = webservice.named_post(334 >>> response = webservice.named_post(
268 ... '/package-sets/mozilla', 'addSubsets', {},335 ... '/package-sets/hoary/mozilla', 'addSubsets', {},
269 ... names=[u'firefox', u'thunderbird'])336 ... names=[u'firefox', u'thunderbird'])
270 >>> print response337 >>> print response
271 HTTP/1.1 200 Ok338 HTTP/1.1 200 Ok
@@ -275,7 +342,7 @@
275non-existing package sets will not fail.342non-existing package sets will not fail.
276343
277 >>> response = webservice.named_post(344 >>> response = webservice.named_post(
278 ... '/package-sets/thunderbird', 'addSubsets', {},345 ... '/package-sets/hoary/thunderbird', 'addSubsets', {},
279 ... names=[u'does-not-exist'])346 ... names=[u'does-not-exist'])
280 >>> print response347 >>> print response
281 HTTP/1.1 200 Ok348 HTTP/1.1 200 Ok
@@ -283,7 +350,7 @@
283 null350 null
284351
285 >>> response = webservice.named_post(352 >>> response = webservice.named_post(
286 ... '/package-sets/thunderbird', 'removeSubsets', {},353 ... '/package-sets/hoary/thunderbird', 'removeSubsets', {},
287 ... names=[u'does-not-exist'])354 ... names=[u'does-not-exist'])
288 >>> print response355 >>> print response
289 HTTP/1.1 200 Ok356 HTTP/1.1 200 Ok
@@ -293,49 +360,49 @@
293The 'umbrella' package set should have plenty of subsets now.360The 'umbrella' package set should have plenty of subsets now.
294361
295 >>> response = webservice.named_get(362 >>> response = webservice.named_get(
296 ... '/package-sets/umbrella', 'setsIncluded', {})363 ... '/package-sets/hoary/umbrella', 'setsIncluded', {})
297 >>> print_payload(response)364 >>> print_payload(response)
298 http://api.launchpad.dev/beta/package-sets/firefox365 http://api.launchpad.dev/beta/package-sets/hoary/firefox
299 http://api.launchpad.dev/beta/package-sets/gnome366 http://api.launchpad.dev/beta/package-sets/hoary/gnome
300 http://api.launchpad.dev/beta/package-sets/languagepack367 http://api.launchpad.dev/beta/package-sets/hoary/languagepack
301 http://api.launchpad.dev/beta/package-sets/mozilla368 http://api.launchpad.dev/beta/package-sets/hoary/mozilla
302 http://api.launchpad.dev/beta/package-sets/thunderbird369 http://api.launchpad.dev/beta/package-sets/hoary/thunderbird
303370
304However only two of the above are direct subsets.371However only two of the above are direct subsets.
305372
306 >>> response = webservice.named_get(373 >>> response = webservice.named_get(
307 ... '/package-sets/umbrella', 'setsIncluded', {},374 ... '/package-sets/hoary/umbrella', 'setsIncluded', {},
308 ... direct_inclusion=True)375 ... direct_inclusion=True)
309 >>> print_payload(response)376 >>> print_payload(response)
310 http://api.launchpad.dev/beta/package-sets/gnome377 http://api.launchpad.dev/beta/package-sets/hoary/gnome
311 http://api.launchpad.dev/beta/package-sets/mozilla378 http://api.launchpad.dev/beta/package-sets/hoary/mozilla
312379
313Let's ask the question the other way around what package sets are including380Let's ask the question the other way around what package sets are including
314a particular subset?381a particular subset?
315382
316 >>> response = webservice.named_get(383 >>> response = webservice.named_get(
317 ... '/package-sets/languagepack', 'setsIncludedBy', {})384 ... '/package-sets/hoary/languagepack', 'setsIncludedBy', {})
318 >>> print_payload(response)385 >>> print_payload(response)
319 http://api.launchpad.dev/beta/package-sets/gnome386 http://api.launchpad.dev/beta/package-sets/hoary/gnome
320 http://api.launchpad.dev/beta/package-sets/mozilla387 http://api.launchpad.dev/beta/package-sets/hoary/mozilla
321 http://api.launchpad.dev/beta/package-sets/thunderbird388 http://api.launchpad.dev/beta/package-sets/hoary/thunderbird
322 http://api.launchpad.dev/beta/package-sets/umbrella389 http://api.launchpad.dev/beta/package-sets/hoary/umbrella
323390
324The list of package sets that *directly* include 'languagepack' will be391The list of package sets that *directly* include 'languagepack' will be
325shorter because the transitive closure is ignored.392shorter because the transitive closure is ignored.
326393
327 >>> response = webservice.named_get(394 >>> response = webservice.named_get(
328 ... '/package-sets/languagepack', 'setsIncludedBy', {},395 ... '/package-sets/hoary/languagepack', 'setsIncludedBy', {},
329 ... direct_inclusion=True)396 ... direct_inclusion=True)
330 >>> print_payload(response)397 >>> print_payload(response)
331 http://api.launchpad.dev/beta/package-sets/gnome398 http://api.launchpad.dev/beta/package-sets/hoary/gnome
332 http://api.launchpad.dev/beta/package-sets/thunderbird399 http://api.launchpad.dev/beta/package-sets/hoary/thunderbird
333400
334We can remove subsets as well. In the example below 'thunderbird' will401We can remove subsets as well. In the example below 'thunderbird' will
335stop including 'languagepack'.402stop including 'languagepack'.
336403
337 >>> response = webservice.named_post(404 >>> response = webservice.named_post(
338 ... '/package-sets/thunderbird', 'removeSubsets', {},405 ... '/package-sets/hoary/thunderbird', 'removeSubsets', {},
339 ... names=[u'languagepack'])406 ... names=[u'languagepack'])
340 >>> print response407 >>> print response
341 HTTP/1.1 200 Ok408 HTTP/1.1 200 Ok
@@ -344,37 +411,37 @@
344And, here we go, now 'languagepack' has only one direct predecessor: 'gnome'.411And, here we go, now 'languagepack' has only one direct predecessor: 'gnome'.
345412
346 >>> response = webservice.named_get(413 >>> response = webservice.named_get(
347 ... '/package-sets/languagepack', 'setsIncludedBy', {},414 ... '/package-sets/hoary/languagepack', 'setsIncludedBy', {},
348 ... direct_inclusion=True)415 ... direct_inclusion=True)
349 >>> print_payload(response)416 >>> print_payload(response)
350 http://api.launchpad.dev/beta/package-sets/gnome417 http://api.launchpad.dev/beta/package-sets/hoary/gnome
351418
352Let's add a few source packages to the 'firefox' and the 'thunderbird'419Let's add a few source packages to the 'firefox' and the 'thunderbird'
353package sets.420package sets.
354421
355 >>> response = webservice.named_post(422 >>> response = webservice.named_post(
356 ... '/package-sets/firefox', 'addSources', {},423 ... '/package-sets/hoary/firefox', 'addSources', {},
357 ... names=['at', 'mozilla-firefox', 'language-pack-de'])424 ... names=['at', 'mozilla-firefox', 'language-pack-de'])
358 >>> print response425 >>> print response
359 HTTP/1.1 200 Ok426 HTTP/1.1 200 Ok
360 ...427 ...
361428
362 >>> response = webservice.named_get(429 >>> response = webservice.named_get(
363 ... '/package-sets/firefox', 'getSourcesIncluded', {})430 ... '/package-sets/hoary/firefox', 'getSourcesIncluded', {})
364 >>> print response431 >>> print response
365 HTTP/1.1 200 Ok432 HTTP/1.1 200 Ok
366 ...433 ...
367 ["at", "language-pack-de", "mozilla-firefox"]434 ["at", "language-pack-de", "mozilla-firefox"]
368435
369 >>> response = webservice.named_post(436 >>> response = webservice.named_post(
370 ... '/package-sets/thunderbird', 'addSources', {},437 ... '/package-sets/hoary/thunderbird', 'addSources', {},
371 ... names=['at', 'cnews', 'thunderbird', 'language-pack-de'])438 ... names=['at', 'cnews', 'thunderbird', 'language-pack-de'])
372 >>> print response439 >>> print response
373 HTTP/1.1 200 Ok440 HTTP/1.1 200 Ok
374 ...441 ...
375442
376 >>> response = webservice.named_get(443 >>> response = webservice.named_get(
377 ... '/package-sets/thunderbird', 'getSourcesIncluded', {})444 ... '/package-sets/hoary/thunderbird', 'getSourcesIncluded', {})
378 >>> print response445 >>> print response
379 HTTP/1.1 200 Ok446 HTTP/1.1 200 Ok
380 ...447 ...
@@ -386,9 +453,9 @@
386 ... '/package-sets/', 'setsIncludingSource', {},453 ... '/package-sets/', 'setsIncludingSource', {},
387 ... sourcepackagename=u'mozilla-firefox')454 ... sourcepackagename=u'mozilla-firefox')
388 >>> print_payload(response)455 >>> print_payload(response)
389 http://api.launchpad.dev/beta/package-sets/firefox456 http://api.launchpad.dev/beta/package-sets/hoary/firefox
390 http://api.launchpad.dev/beta/package-sets/mozilla457 http://api.launchpad.dev/beta/package-sets/hoary/mozilla
391 http://api.launchpad.dev/beta/package-sets/umbrella458 http://api.launchpad.dev/beta/package-sets/hoary/umbrella
392459
393Which package sets include the 'mozilla-firefox' source package *directly*?460Which package sets include the 'mozilla-firefox' source package *directly*?
394461
@@ -397,8 +464,8 @@
397 ... sourcepackagename=u'mozilla-firefox',464 ... sourcepackagename=u'mozilla-firefox',
398 ... direct_inclusion=True)465 ... direct_inclusion=True)
399 >>> print_payload(response)466 >>> print_payload(response)
400 http://api.launchpad.dev/beta/package-sets/firefox467 http://api.launchpad.dev/beta/package-sets/hoary/firefox
401 http://api.launchpad.dev/beta/package-sets/umbrella468 http://api.launchpad.dev/beta/package-sets/hoary/umbrella
402469
403If a non-existing source package name is passed it returns an error.470If a non-existing source package name is passed it returns an error.
404471
@@ -414,9 +481,9 @@
414What source packages are shared by the 'firefox' and the 'thunderbird'481What source packages are shared by the 'firefox' and the 'thunderbird'
415package sets?482package sets?
416483
417 >>> thunderbird = webservice.get("/package-sets/thunderbird").jsonBody()484 >>> thunderbird = webservice.get("/package-sets/hoary/thunderbird").jsonBody()
418 >>> response = webservice.named_get(485 >>> response = webservice.named_get(
419 ... '/package-sets/firefox', 'getSourcesSharedBy', {},486 ... '/package-sets/hoary/firefox', 'getSourcesSharedBy', {},
420 ... other_package_set=thunderbird['self_link'])487 ... other_package_set=thunderbird['self_link'])
421 >>> print response488 >>> print response
422 HTTP/1.1 200 Ok489 HTTP/1.1 200 Ok
@@ -426,16 +493,16 @@
426How about the complement set i.e. the packages not shared?493How about the complement set i.e. the packages not shared?
427494
428 >>> response = webservice.named_get(495 >>> response = webservice.named_get(
429 ... '/package-sets/firefox', 'getSourcesNotSharedBy', {},496 ... '/package-sets/hoary/firefox', 'getSourcesNotSharedBy', {},
430 ... other_package_set=thunderbird['self_link'])497 ... other_package_set=thunderbird['self_link'])
431 >>> print response498 >>> print response
432 HTTP/1.1 200 Ok499 HTTP/1.1 200 Ok
433 ...500 ...
434 ["mozilla-firefox"]501 ["mozilla-firefox"]
435502
436 >>> firefox = webservice.get("/package-sets/firefox").jsonBody()503 >>> firefox = webservice.get("/package-sets/hoary/firefox").jsonBody()
437 >>> response = webservice.named_get(504 >>> response = webservice.named_get(
438 ... '/package-sets/thunderbird', 'getSourcesNotSharedBy', {},505 ... '/package-sets/hoary/thunderbird', 'getSourcesNotSharedBy', {},
439 ... other_package_set=firefox['self_link'])506 ... other_package_set=firefox['self_link'])
440 >>> print response507 >>> print response
441 HTTP/1.1 200 Ok508 HTTP/1.1 200 Ok
@@ -461,7 +528,7 @@
461 >>> response = webservice.named_post(528 >>> response = webservice.named_post(
462 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},529 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
463 ... person=name12['self_link'],530 ... person=name12['self_link'],
464 ... packageset='firefox')531 ... packageset=firefox['self_link'])
465 >>> print response532 >>> print response
466 HTTP/1.1 201 Created533 HTTP/1.1 201 Created
467 ...534 ...
@@ -489,7 +556,7 @@
489 >>> response = webservice.named_post(556 >>> response = webservice.named_post(
490 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},557 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
491 ... person=name12['self_link'],558 ... person=name12['self_link'],
492 ... packageset='mozilla')559 ... packageset=mozilla['self_link'])
493 >>> print response560 >>> print response
494 HTTP/1.1 201 Created561 HTTP/1.1 201 Created
495 ...562 ...
@@ -499,7 +566,7 @@
499566
500 >>> response = webservice.named_get(567 >>> response = webservice.named_get(
501 ... ubuntu['main_archive_link'], 'getUploadersForPackageset', {},568 ... ubuntu['main_archive_link'], 'getUploadersForPackageset', {},
502 ... packageset='firefox')569 ... packageset=firefox['self_link'])
503 >>> print_payload(response)570 >>> print_payload(response)
504 http://.../+archive/primary/+upload/name12?type=packageset&item=firefox571 http://.../+archive/primary/+upload/name12?type=packageset&item=firefox
505572
@@ -508,7 +575,7 @@
508575
509 >>> response = webservice.named_get(576 >>> response = webservice.named_get(
510 ... ubuntu['main_archive_link'], 'getUploadersForPackageset', {},577 ... ubuntu['main_archive_link'], 'getUploadersForPackageset', {},
511 ... packageset='firefox', direct_permissions=False)578 ... packageset=firefox['self_link'], direct_permissions=False)
512 >>> print_payload(response)579 >>> print_payload(response)
513 http://.../+archive/primary/+upload/name12?type=packageset&item=firefox580 http://.../+archive/primary/+upload/name12?type=packageset&item=firefox
514 http://.../+archive/primary/+upload/name12?type=packageset&item=mozilla581 http://.../+archive/primary/+upload/name12?type=packageset&item=mozilla
@@ -518,7 +585,7 @@
518 >>> response = webservice.named_post(585 >>> response = webservice.named_post(
519 ... ubuntu['main_archive_link'], 'deletePackagesetUploader', {},586 ... ubuntu['main_archive_link'], 'deletePackagesetUploader', {},
520 ... person=name12['self_link'],587 ... person=name12['self_link'],
521 ... packageset='mozilla')588 ... packageset=mozilla['self_link'])
522 >>> print response589 >>> print response
523 HTTP/1.1 200 Ok590 HTTP/1.1 200 Ok
524 ...591 ...
@@ -528,7 +595,7 @@
528595
529 >>> response = webservice.named_get(596 >>> response = webservice.named_get(
530 ... ubuntu['main_archive_link'], 'getUploadersForPackageset', {},597 ... ubuntu['main_archive_link'], 'getUploadersForPackageset', {},
531 ... packageset='firefox', direct_permissions=False)598 ... packageset=firefox['self_link'], direct_permissions=False)
532 >>> print_payload(response)599 >>> print_payload(response)
533 http://.../+archive/primary/+upload/name12?type=packageset&item=firefox600 http://.../+archive/primary/+upload/name12?type=packageset&item=firefox
534601
@@ -538,7 +605,7 @@
538 >>> response = webservice.named_post(605 >>> response = webservice.named_post(
539 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},606 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
540 ... person=cprov['self_link'],607 ... person=cprov['self_link'],
541 ... packageset='mozilla')608 ... packageset=mozilla['self_link'])
542 >>> print response609 >>> print response
543 HTTP/1.1 201 Created610 HTTP/1.1 201 Created
544 ...611 ...
@@ -546,7 +613,7 @@
546 >>> response = webservice.named_post(613 >>> response = webservice.named_post(
547 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},614 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
548 ... person=cprov['self_link'],615 ... person=cprov['self_link'],
549 ... packageset='thunderbird')616 ... packageset=thunderbird['self_link'])
550 >>> print response617 >>> print response
551 HTTP/1.1 201 Created618 HTTP/1.1 201 Created
552 ...619 ...
@@ -599,6 +666,39 @@
599 ...666 ...
600 true667 true
601668
669Archive permissions have distro series scope. We did not specify a distro
670series in the query above. Hence the `currentseries` in Ubuntu is assumed
671('hoary').
672The following query (note the additional 'distroseries' parameter) is
673thus equivalent:
674
675 >>> print ubuntu['current_series_link']
676 http://api.launchpad.dev/beta/ubuntu/hoary
677 >>> hoary = webservice.get("/ubuntu/hoary").jsonBody()
678 >>> print hoary['self_link']
679 http://api.launchpad.dev/beta/ubuntu/hoary
680
681 >>> response = webservice.named_get(
682 ... ubuntu['main_archive_link'], 'isSourceUploadAllowed',
683 ... {}, sourcepackagename='mozilla-firefox',
684 ... person=cprov['self_link'], distroseries=hoary['self_link'])
685 >>> print(response)
686 HTTP/1.1 200 Ok
687 ...
688 true
689
690Since cprov's upload permission is limited to the current distro series
691('hoary') checking the same permission for 'grumpy' will fail.
692
693 >>> response = webservice.named_get(
694 ... ubuntu['main_archive_link'], 'isSourceUploadAllowed',
695 ... {}, sourcepackagename='mozilla-firefox',
696 ... person=cprov['self_link'], distroseries=grumpy['self_link'])
697 >>> print(response)
698 HTTP/1.1 200 Ok
699 ...
700 false
701
602'name12' should not be allowed to upload the 'thunderbird' source package.702'name12' should not be allowed to upload the 'thunderbird' source package.
603703
604 >>> response = webservice.named_get(704 >>> response = webservice.named_get(
@@ -610,6 +710,80 @@
610 ...710 ...
611 false711 false
612712
713Let's create a (related) package set in 'grumpy' and authorize 'name12' to
714upload to it.
715
716This will fail since 'name12' has no permissions applying to 'grumpy' yet.
717
718 >>> response = webservice.named_get(
719 ... ubuntu['main_archive_link'], 'isSourceUploadAllowed',
720 ... {}, sourcepackagename='thunderbird',
721 ... person=name12['self_link'], distroseries=grumpy['self_link'])
722 >>> print(response)
723 HTTP/1.1 200 Ok
724 ...
725 false
726
727Create a new package set ('grouchy-thunderbird') in 'grumpy'.
728
729 >>> response = webservice.named_post(
730 ... '/package-sets', 'new', {},
731 ... name=u'grouchy-thunderbird',
732 ... description=u'Contains all thunderbird packages in grumpy',
733 ... owner=name12['self_link'], distroseries=grumpy['self_link'],
734 ... related_set=thunderbird['self_link'])
735 >>> print response
736 HTTP/1.1 201 Created
737 ...
738
739 >>> response = webservice.named_get(
740 ... thunderbird['self_link'], 'relatedSets', {})
741 >>> print_payload(response)
742 http://api.launchpad.dev/beta/package-sets/grumpy/grouchy-thunderbird
743
744Associate 'grouchy-thunderbird' with the appropriate source packages.
745
746 >>> response = webservice.named_post(
747 ... '/package-sets/grumpy/grouchy-thunderbird', 'addSources', {},
748 ... names=['thunderbird', 'language-pack-de'])
749 >>> print response
750 HTTP/1.1 200 Ok
751 ...
752
753Grant 'name12' upload permissions to 'grouchy-thunderbird' in 'grumpy'.
754
755 >>> grouchy_bird = webservice.get(
756 ... "/package-sets/grumpy/grouchy-thunderbird").jsonBody()
757
758 >>> response = webservice.named_post(
759 ... ubuntu['main_archive_link'], 'newPackagesetUploader', {},
760 ... person=name12['self_link'],
761 ... packageset=grouchy_bird['self_link'])
762 >>> print response
763 HTTP/1.1 201 Created
764 ...
765
766Does the new archive permission show up?
767
768 >>> response = webservice.named_get(
769 ... ubuntu['main_archive_link'], 'getPackagesetsForUploader', {},
770 ... person=name12['self_link'])
771 >>> print_payload(response)
772 http://...+archive/primary/+upload/name12?type=packageset&item=firefox
773 http://...+archive/primary/+upload/name12?type=packageset&item=grouchy-thunderbird
774
775And now 'name12' should be authorized to upload source package
776'thunderbird' in 'grumpy'.
777
778 >>> response = webservice.named_get(
779 ... ubuntu['main_archive_link'], 'isSourceUploadAllowed',
780 ... {}, sourcepackagename='thunderbird',
781 ... person=name12['self_link'], distroseries=grumpy['self_link'])
782 >>> print(response)
783 HTTP/1.1 200 Ok
784 ...
785 true
786
613Sometimes it's also interesting to see what package set based upload787Sometimes it's also interesting to see what package set based upload
614permissions apply to a source package irrespective of the principal.788permissions apply to a source package irrespective of the principal.
615789
616790
=== 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-03 18:13:39 +0000
@@ -34,7 +34,7 @@
34 </td>34 </td>
35 <td>35 <td>
36 <tal:active condition="token">36 <tal:active condition="token">
37 <a tal:attributes="href subscription/fmt:url" class="info">37 <a tal:attributes="href subscription/fmt:url" class="sprite info">
38 View38 View
39 </a>39 </a>
40 </tal:active>40 </tal:active>
4141
=== added file 'lib/lp/soyuz/tests/test_packageset.py'
--- lib/lp/soyuz/tests/test_packageset.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/tests/test_packageset.py 2009-11-03 18:13:43 +0000
@@ -0,0 +1,187 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Test Packageset features."""
5
6from zope.component import getUtility
7
8from canonical.testing import LaunchpadZopelessLayer
9
10from lp.testing import TestCaseWithFactory
11from lp.registry.interfaces.distribution import IDistributionSet
12from lp.registry.interfaces.distroseries import DistroSeriesStatus
13from lp.soyuz.interfaces.packageset import (
14 DuplicatePackagesetName, IPackagesetSet)
15
16
17class TestPackagesetSet(TestCaseWithFactory):
18
19 layer = LaunchpadZopelessLayer
20
21 def setUp(self):
22 """Setup a distribution with multiple distroseries."""
23 super(TestPackagesetSet, self).setUp()
24 self.distribution = getUtility(IDistributionSet).getByName(
25 'ubuntu')
26 self.distroseries_current = self.distribution.currentseries
27 self.distroseries_experimental = self.factory.makeDistroRelease(
28 distribution = self.distribution, name="experimental",
29 status=DistroSeriesStatus.EXPERIMENTAL)
30
31 self.person1 = self.factory.makePerson(
32 name='hacker', displayname=u'Happy Hacker')
33
34 self.packageset_set = getUtility(IPackagesetSet)
35
36 def test_new_defaults_to_current_distroseries(self):
37 # If the distroseries is not provided, the current development
38 # distroseries will be assumed.
39 packageset = self.packageset_set.new(
40 u'kernel', u'Contains all OS kernel packages', self.person1)
41
42 self.failUnlessEqual(
43 self.distroseries_current, packageset.distroseries)
44
45 def test_new_with_specified_distroseries(self):
46 # A distroseries can be provided when creating a package set.
47 packageset = self.packageset_set.new(
48 u'kernel', u'Contains all OS kernel packages', self.person1,
49 distroseries=self.distroseries_experimental)
50
51 self.failUnlessEqual(
52 self.distroseries_experimental, packageset.distroseries)
53
54 def test_new_creates_new_packageset_group(self):
55 # Creating a new packageset should also create a new packageset
56 # group with the same owner.
57 packageset = self.packageset_set.new(
58 u'kernel', u'Contains all OS kernel packages', self.person1,
59 distroseries=self.distroseries_experimental)
60
61 self.failUnlessEqual(
62 self.person1, packageset.packagesetgroup.owner)
63
64 def test_new_duplicate_name_for_same_distroseries(self):
65 # Creating a packageset with a duplicate name for the
66 # given distroseries will fail.
67 packageset = self.packageset_set.new(
68 u'kernel', u'Contains all OS kernel packages', self.person1,
69 distroseries=self.distroseries_experimental)
70
71 self.failUnlessRaises(
72 DuplicatePackagesetName, self.packageset_set.new,
73 u'kernel', u'A packageset with a duplicate name', self.person1,
74 distroseries=self.distroseries_experimental)
75
76 def test_new_duplicate_name_for_different_distroseries(self):
77 # Creating a packageset with a duplicate name but for a different
78 # series is no problem.
79 packageset = self.packageset_set.new(
80 u'kernel', u'Contains all OS kernel packages', self.person1)
81
82 packageset2 = self.packageset_set.new(
83 u'kernel', u'A packageset with a duplicate name', self.person1,
84 distroseries=self.distroseries_experimental)
85 self.assertEqual(packageset.name, packageset2.name)
86
87 def test_new_related_packageset(self):
88 # Creating a new package set while specifying a `related_set` should
89 # have the effect that the former ends up in the same group as the
90 # latter.
91 pset1 = self.packageset_set.new(
92 u'kernel', u'Contains all OS kernel packages', self.person1)
93
94 pset2 = self.packageset_set.new(
95 u'kernel', u'A related package set.', self.person1,
96 distroseries=self.distroseries_experimental, related_set=pset1)
97 self.assertEqual(pset1.packagesetgroup, pset2.packagesetgroup)
98
99 def test_get_by_name_in_current_distroseries(self):
100 # IPackagesetSet.getByName() will return the package set in the
101 # current distroseries if the optional `distroseries` parameter is
102 # omitted.
103 pset1 = self.packageset_set.new(
104 u'kernel', u'Contains all OS kernel packages', self.person1)
105 pset2 = self.packageset_set.new(
106 u'kernel', u'A related package set.', self.person1,
107 distroseries=self.distroseries_experimental, related_set=pset1)
108 pset_found = getUtility(IPackagesetSet).getByName('kernel')
109 self.assertEqual(pset1, pset_found)
110
111 def test_get_by_name_in_specified_distroseries(self):
112 # IPackagesetSet.getByName() will return the package set in the
113 # specified distroseries.
114 pset1 = self.packageset_set.new(
115 u'kernel', u'Contains all OS kernel packages', self.person1)
116 pset2 = self.packageset_set.new(
117 u'kernel', u'A related package set.', self.person1,
118 distroseries=self.distroseries_experimental, related_set=pset1)
119 pset_found = getUtility(IPackagesetSet).getByName(
120 'kernel', distroseries=self.distroseries_experimental)
121 self.assertEqual(pset2, pset_found)
122
123
124class TestPackageset(TestCaseWithFactory):
125
126 layer = LaunchpadZopelessLayer
127
128 def setUp(self):
129 """Setup a distribution with multiple distroseries."""
130 super(TestPackageset, self).setUp()
131 self.distribution = getUtility(IDistributionSet).getByName(
132 'ubuntu')
133 self.distroseries_current = self.distribution.currentseries
134 self.distroseries_experimental = self.factory.makeDistroRelease(
135 distribution = self.distribution, name="experimental",
136 status=DistroSeriesStatus.EXPERIMENTAL)
137 self.distroseries_experimental2 = self.factory.makeDistroRelease(
138 distribution = self.distribution, name="experimental2",
139 status=DistroSeriesStatus.EXPERIMENTAL)
140
141 self.person1 = self.factory.makePerson(
142 name='hacker', displayname=u'Happy Hacker')
143
144 self.packageset_set = getUtility(IPackagesetSet)
145
146 def test_no_related_sets(self):
147 # If the package set is the only one in the group the result set
148 # returned by relatedSets() is empty.
149 packageset = self.packageset_set.new(
150 u'kernel', u'Contains all OS kernel packages', self.person1)
151
152 self.failUnlessEqual(packageset.relatedSets().count(), 0)
153
154 def test_related_set_found(self):
155 # Creating a new package set while specifying a `related_set` should
156 # have the effect that the former ends up in the same group as the
157 # latter.
158
159 # The original package set.
160 pset1 = self.packageset_set.new(
161 u'kernel', u'Contains all OS kernel packages', self.person1)
162
163 # A related package set.
164 pset2 = self.packageset_set.new(
165 u'kernel', u'A related package set.', self.person1,
166 distroseries=self.distroseries_experimental, related_set=pset1)
167 self.assertEqual(pset1.packagesetgroup, pset2.packagesetgroup)
168
169 # An unrelated package set with the same name.
170 pset3 = self.packageset_set.new(
171 u'kernel', u'Unrelated package set.', self.person1,
172 distroseries=self.distroseries_experimental2)
173 self.assertNotEqual(pset2.packagesetgroup, pset3.packagesetgroup)
174
175 # Make sure 'pset2' is related to 'pset1'.
176 related = pset1.relatedSets()
177 self.assertEqual(related.count(), 1)
178 self.assertEqual(related[0], pset2)
179
180 # And the other way around ..
181 related = pset2.relatedSets()
182 self.assertEqual(related.count(), 1)
183 self.assertEqual(related[0], pset1)
184
185 # Unsurprisingly, the unrelated package set is not associated with any
186 # other package set.
187 self.failUnlessEqual(pset3.relatedSets().count(), 0)
0188
=== modified file 'scripts/ftpmaster-tools/_syncorigins.py'
--- scripts/ftpmaster-tools/_syncorigins.py 2009-07-23 02:33:14 +0000
+++ scripts/ftpmaster-tools/_syncorigins.py 2009-11-03 18:13:42 +0000
@@ -12,7 +12,7 @@
12"debian": {12"debian": {
13 "name": "Debian",13 "name": "Debian",
14 "url": "http://ftp.debian.org/debian/",14 "url": "http://ftp.debian.org/debian/",
15 "default suite": "unstable",15 "default suite": "testing",
16 "default component": "main",16 "default component": "main",
17 "dsc": "must be signed and valid"17 "dsc": "must be signed and valid"
18 },18 },
1919
=== modified file 'utilities/pgmassacre.py'
--- utilities/pgmassacre.py 2009-06-24 20:15:50 +0000
+++ utilities/pgmassacre.py 2009-11-03 18:13:41 +0000
@@ -11,6 +11,7 @@
1111
12# Nothing but system installed libraries - this script sometimes12# Nothing but system installed libraries - this script sometimes
13# gets installed standalone with no Launchpad tree available.13# gets installed standalone with no Launchpad tree available.
14from distutils.version import LooseVersion
14import sys15import sys
15import time16import time
16import psycopg217import psycopg2
@@ -184,12 +185,20 @@
184 error_msg = None185 error_msg = None
185 con = connect()186 con = connect()
186 con.set_isolation_level(0) # Autocommit required for CREATE DATABASE.187 con.set_isolation_level(0) # Autocommit required for CREATE DATABASE.
188 create_db_cmd = """
189 CREATE DATABASE %s WITH ENCODING='UTF8' TEMPLATE=%s
190 """ % (database, template)
191 # 8.4 allows us to create empty databases with a different locale
192 # to template1 by using the template0 database as a template.
193 # We make use of this feature so we don't have to care what locale
194 # was used to create the database cluster rather than requiring it
195 # to be rebuilt in the C locale.
196 if pg_version >= LooseVersion("8.4.0") and template == "template0":
197 create_db_cmd += "LC_COLLATE='C' LC_CTYPE='C'"
187 while now < start + 20:198 while now < start + 20:
188 cur = con.cursor()199 cur = con.cursor()
189 try:200 try:
190 cur.execute(201 cur.execute(create_db_cmd)
191 "CREATE DATABASE %s WITH ENCODING='UTF8' TEMPLATE=%s"
192 % (database, template))
193 con.close()202 con.close()
194 return 0203 return 0
195 except psycopg2.Error, exception:204 except psycopg2.Error, exception:
@@ -219,6 +228,7 @@
219228
220229
221options = None230options = None
231pg_version = None # LooseVersion - Initialized in main()
222232
223233
224def main():234def main():
@@ -243,6 +253,12 @@
243253
244 con = connect()254 con = connect()
245 cur = con.cursor()255 cur = con.cursor()
256
257 # Store the database version for version specific code.
258 global pg_version
259 cur.execute("show server_version")
260 pg_version = LooseVersion(cur.fetchone()[0])
261
246 # Ensure the template database exists.262 # Ensure the template database exists.
247 if options.template is not None:263 if options.template is not None:
248 cur.execute(264 cur.execute(