← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~leonardr/launchpad/explicit-versions into lp:launchpad

 

Leonard Richardson has proposed merging lp:~leonardr/launchpad/explicit-versions into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~leonardr/launchpad/explicit-versions/+merge/54558

This branch makes four changes:

1. A tiny change to lib/canonical/launchpad/rest/configuration.py, to turn on explicit version enforcement. From now on, if you publish an entry, a field, or a named operation, you must use as_of (or, for named operations, @operation_for_version)

2. Two new helper methods in apihelpers.py: patch_entry_explicit_version and patch_operation_explicit_version. These helpers let us grandfather in entries and operations that were defined before we turned on explicit version enforcement. The exceptions raised by patch_operation_explicit_version were useful to me while getting everything working, and will be useful in the future as we fix the operations that are currently grandfathered in.

3. A huge number of calls to the helper methods added to _schema_circular_imports, grandfathering in almost all of the existing entries and named operations to version 'beta' of the web service.

4. A large number of changes to the published entries and operations, made in cases where it was easier to actually fix the entry/operation than to grandfather in the change to be fixed later.

#4 is the one that needs the most explanation. Here's one common idiom:

-        vocabulary=ServiceUsage),
-        ('devel', {'exported': True}),
-        exported=False,
-        )
+        vocabulary=ServiceUsage), as_of="devel")

This field is exported starting in the 'devel' version, and not before. With our configuration, lazr.restful now requires an explicit 'as_of' argument when exporting a field. As a bonus, as_of="devel" is equivalent to 
"('devel', {'exported': True}), exported=False," so the syntax is now much nicer.

I added as_of="beta" to fix some entries that had some fields exported in beta and other fields exported in devel.

I added @operation_for_version('beta') to a large number of methods, rather than grandfathering them in in apihelpers.py. I did this for methods that were defined in mixin interfaces such as IHasBugs. This is an artifact of the fact that when I generated the code that's currently in apihelpers.py, it did not say this:

   patch_operation_explicit_version(IHasBugs, "operation", "beta")

Instead, it said this:

   patch_operation_explicit_version(IInterface1, "operation", "beta")
...
   patch_operation_explicit_version(IInterface2, "operation", "beta")
...
   patch_operation_explicit_version(IInterface3, "operation", "beta")

Where IInterface1, IInterface2, ... are the interfaces that mix in IHasBugs. Since I already had to determine that the mixin interface for this particular operation was IHasBugs, I went ahead and fixed the operation at the source, which let me get rid of all three lines.
-- 
https://code.launchpad.net/~leonardr/launchpad/explicit-versions/+merge/54558
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~leonardr/launchpad/explicit-versions into lp:launchpad.
=== modified file 'database/schema/security.cfg'
=== modified file 'lib/canonical/launchpad/components/apihelpers.py'
--- lib/canonical/launchpad/components/apihelpers.py	2011-01-28 10:03:12 +0000
+++ lib/canonical/launchpad/components/apihelpers.py	2011-03-23 16:18:44 +0000
@@ -24,6 +24,9 @@
     'patch_reference_property',
     ]
 
+
+from zope.schema import getFields
+
 from lazr.restful.declarations import LAZR_WEBSERVICE_EXPORTED
 
 
@@ -126,3 +129,53 @@
     exported_class[method_name].queryTaggedValue(
         LAZR_WEBSERVICE_EXPORTED)[
             'params'][param_name].vocabulary = vocabulary
+
+
+def patch_entry_explicit_version(interface, version):
+    """Make it look as though an entry definition used as_of.
+
+    This function should be phased out in favor of actually using
+    as_of. This function patches the entry's fields as well as the
+    entry itself. Fields that are explicitly published as of a given
+    version (even though the entry is not) are ignored.
+    """
+    tagged = interface.getTaggedValue(LAZR_WEBSERVICE_EXPORTED)
+    versioned = tagged.dict_for_name(version) or tagged.dict_for_name(None)
+    versioned['_as_of_was_used'] = True
+
+    # Now tag the fields.
+    for name, field in getFields(interface).items():
+        tagged = field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED)
+        if tagged is None:
+            continue
+        versioned = tagged.dict_for_name(version) or tagged.dict_for_name(None)
+        if versioned is None:
+            # This field is explicitly published in some other version.
+            # Just ignore it.
+            continue
+        else:
+            versioned['_as_of_was_used'] = True
+
+
+def patch_operation_explicit_version(interface, method_name, version):
+    """Make it look like operation's first tag was @operation_for_version.
+
+    This function should be phased out in favor of actually using
+    @operation_for_version, everywhere.
+    """
+    tagged = interface[method_name].getTaggedValue(LAZR_WEBSERVICE_EXPORTED)
+    error_prefix = "%s.%s: Attempted to patch to version %s, but " % (
+        interface.__name__, method_name, version)
+    if (len(tagged.stack) > 1
+        and tagged.stack[0].version == None
+        and tagged.stack[1].version == version):
+        raise ValueError(
+            error_prefix + (
+                'it is already published in %s. Did you just change '
+                'it to be explicitly published?' % version))
+    if tagged.stack[0].version == version:
+        raise ValueError(
+            error_prefix + (
+                'it seems to have already been patched. Does this '
+                'method come from a mixin used in multiple interfaces?'))
+    tagged.rename_version(None, version)

=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py	2011-03-10 14:05:51 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py	2011-03-23 16:18:44 +0000
@@ -23,8 +23,10 @@
     patch_choice_vocabulary,
     patch_collection_property,
     patch_collection_return_type,
+    patch_entry_explicit_version,
     patch_entry_return_type,
     patch_list_parameter_type,
+    patch_operation_explicit_version,
     patch_plain_parameter_type,
     patch_reference_property,
     )
@@ -33,6 +35,11 @@
     IMessage,
     IUserToUserEmail,
     )
+from canonical.launchpad.interfaces.emailaddress import IEmailAddress
+from canonical.launchpad.interfaces.temporaryblobstorage import (
+    ITemporaryBlobStorage,
+    ITemporaryStorageManager,
+    )
 from lp.blueprints.interfaces.specification import ISpecification
 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
 from lp.blueprints.interfaces.specificationtarget import (
@@ -44,9 +51,12 @@
     IBug,
     IFrontPageBugAddForm,
     )
+from lp.bugs.interfaces.bugactivity import IBugActivity
+from lp.bugs.interfaces.bugattachment import IBugAttachment
 from lp.bugs.interfaces.bugbranch import IBugBranch
 from lp.bugs.interfaces.bugnomination import IBugNomination
 from lp.bugs.interfaces.bugsubscriptionfilter import IBugSubscriptionFilter
+from lp.bugs.interfaces.bugsubscription import IBugSubscription
 from lp.bugs.interfaces.bugtarget import (
     IBugTarget,
     IHasBugs,
@@ -56,13 +66,28 @@
     IBugTracker,
     IBugTrackerComponent,
     IBugTrackerComponentGroup,
+    IBugTrackerSet,
     )
 from lp.bugs.interfaces.bugwatch import IBugWatch
+from lp.bugs.interfaces.cve import ICve
+from lp.bugs.interfaces.malone import IMaloneApplication
+from lp.bugs.interfaces.structuralsubscription import (
+    IStructuralSubscription,
+    IStructuralSubscriptionTarget,
+    )
 from lp.buildmaster.enums import BuildStatus
+from lp.buildmaster.interfaces.builder import (
+    IBuilder,
+    IBuilderSet,
+    )
 from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
 from lp.buildmaster.interfaces.buildqueue import IBuildQueue
-from lp.code.interfaces.branch import IBranch
+from lp.code.interfaces.branch import (
+    IBranch,
+    IBranchSet,
+    )
 from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
+from lp.code.interfaces.branchmergequeue import IBranchMergeQueue
 from lp.code.interfaces.branchsubscription import IBranchSubscription
 from lp.code.interfaces.codeimport import ICodeImport
 from lp.code.interfaces.codereviewcomment import ICodeReviewComment
@@ -81,43 +106,91 @@
     )
 from lp.hardwaredb.interfaces.hwdb import (
     HWBus,
+    IHWDBApplication,
+    IHWDevice,
+    IHWDeviceClass,
+    IHWDriver,
+    IHWDriverName,
+    IHWDriverPackageName,
     IHWSubmission,
+    IHWSubmissionDevice,
+    IHWVendorID,
     )
+from lp.registry.interfaces.commercialsubscription import ICommercialSubscription
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distributionmirror import IDistributionMirror
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
     )
 from lp.registry.interfaces.distroseries import IDistroSeries
+from lp.registry.interfaces.distroseriesdifference import (
+    IDistroSeriesDifference,
+    )
 from lp.registry.interfaces.distroseriesdifferencecomment import (
     IDistroSeriesDifferenceComment,
     )
+from lp.registry.interfaces.gpg import IGPGKey
+from lp.registry.interfaces.irc import IIrcID
+from lp.registry.interfaces.jabber import IJabberID
+from lp.registry.interfaces.milestone import IHasMilestones
+from lp.registry.interfaces.milestone import IMilestone
 from lp.registry.interfaces.person import (
     IPerson,
     IPersonPublic,
+    IPersonSet,
+    ITeam,
+    )
+from lp.registry.interfaces.pillar import (
+    IPillar,
+    IPillarNameSet,
     )
 from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.registry.interfaces.product import IProduct
-from lp.registry.interfaces.productseries import IProductSeries
+from lp.registry.interfaces.product import (
+    IProduct,
+    IProductSet,
+    )
+from lp.registry.interfaces.productrelease import IProductRelease
+from lp.registry.interfaces.productrelease import IProductReleaseFile
+from lp.registry.interfaces.productseries import (
+    IProductSeries,
+    ITimelineProductSeries,
+    )
+from lp.registry.interfaces.projectgroup import (
+    IProjectGroup,
+    IProjectGroupSet,
+    )
 from lp.registry.interfaces.sourcepackage import ISourcePackage
-from lp.bugs.interfaces.structuralsubscription import (
-    IStructuralSubscription,
-    IStructuralSubscriptionTarget,
-    )
+from lp.registry.interfaces.ssh import ISSHKey
+from lp.registry.interfaces.teammembership import ITeamMembership
+from lp.registry.interfaces.wikiname import IWikiName
 from lp.services.comments.interfaces.conversation import IComment
+from lp.services.worlddata.interfaces.country import (
+    ICountry,
+    ICountrySet,
+    )
+from lp.services.worlddata.interfaces.language import (
+    ILanguage,
+    ILanguageSet,
+    )
 from lp.soyuz.enums import (
     PackagePublishingStatus,
     PackageUploadCustomFormat,
     PackageUploadStatus,
     )
+from lp.soyuz.interfaces.archivedependency import IArchiveDependency
 from lp.soyuz.interfaces.archive import IArchive
-from lp.soyuz.interfaces.archivedependency import IArchiveDependency
 from lp.soyuz.interfaces.archivepermission import IArchivePermission
 from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriber
 from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuild
+from lp.soyuz.interfaces.binarypackagerelease import (
+    IBinaryPackageReleaseDownloadCount,
+    )
 from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
 from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
-from lp.soyuz.interfaces.packageset import IPackageset
+from lp.soyuz.interfaces.packageset import (
+    IPackageset,
+    IPackagesetSet,
+    )
 from lp.soyuz.interfaces.publishing import (
     IBinaryPackagePublishingHistory,
     ISourcePackagePublishingHistory,
@@ -137,11 +210,12 @@
     IPOTemplateSharingSubset,
     IPOTemplateSubset,
     )
+from lp.translations.interfaces.translationgroup import ITranslationGroup
 from lp.translations.interfaces.translationimportqueue import (
+    ITranslationImportQueue,
     ITranslationImportQueueEntry,
     )
 
-
 IBranch['bug_branches'].value_type.schema = IBugBranch
 IBranch['linked_bugs'].value_type.schema = IBug
 IBranch['dependent_branches'].value_type.schema = IBranchMergeProposal
@@ -567,3 +641,276 @@
     IHasSpecifications, 'all_specifications', ISpecification)
 patch_collection_property(
     IHasSpecifications, 'valid_specifications', ISpecification)
+
+
+###
+#
+# Our web service configuration requires that every entry, field, and
+# named operation explicitly name the version in which it first
+# appears. This code grandfathers in entries and named operations that
+# were defined before this rule came into effect. When you change an
+# interface in the future, you should add explicit entry statements to
+# its definition and get rid of the patch calls here.
+#
+###
+
+patch_entry_explicit_version(IArchive, 'beta')
+patch_entry_explicit_version(IArchiveDependency, 'beta')
+patch_entry_explicit_version(IArchivePermission, 'beta')
+patch_entry_explicit_version(IArchiveSubscriber, 'beta')
+patch_entry_explicit_version(IBinaryPackageBuild, 'beta')
+patch_entry_explicit_version(IBinaryPackagePublishingHistory, 'beta')
+patch_entry_explicit_version(IBinaryPackageReleaseDownloadCount, 'beta')
+patch_entry_explicit_version(IBranch, 'beta')
+patch_entry_explicit_version(IBranchMergeProposal, 'beta')
+patch_entry_explicit_version(IBranchMergeQueue, 'beta')
+patch_entry_explicit_version(IBranchSubscription, 'beta')
+patch_entry_explicit_version(IBugActivity, 'beta')
+patch_entry_explicit_version(IBugAttachment, 'beta')
+patch_entry_explicit_version(IBug, 'beta')
+patch_entry_explicit_version(IBugBranch, 'beta')
+patch_entry_explicit_version(IBugNomination, 'beta')
+patch_entry_explicit_version(IBugSubscriptionFilter, 'beta')
+patch_entry_explicit_version(IBugTarget, 'beta')
+patch_entry_explicit_version(IBugTask, 'beta')
+patch_entry_explicit_version(IBugTracker, 'beta')
+patch_entry_explicit_version(IBugTrackerComponent, 'beta')
+patch_entry_explicit_version(IBugTrackerComponentGroup, 'beta')
+patch_entry_explicit_version(IBugWatch, 'beta')
+patch_entry_explicit_version(IBuilder, 'beta')
+patch_entry_explicit_version(ICodeImport, 'beta')
+patch_entry_explicit_version(ICodeReviewComment, 'beta')
+patch_entry_explicit_version(ICodeReviewVoteReference, 'beta')
+patch_entry_explicit_version(ICommercialSubscription, 'beta')
+patch_entry_explicit_version(ICountry, 'beta')
+patch_entry_explicit_version(ICve, 'beta')
+patch_entry_explicit_version(IDistroSeries, 'beta')
+patch_entry_explicit_version(IDistroSeriesDifference, 'beta')
+patch_entry_explicit_version(IDistroSeriesDifferenceComment, 'beta')
+patch_entry_explicit_version(IDistributionMirror, 'beta')
+patch_entry_explicit_version(IDistributionSourcePackage, 'beta')
+patch_entry_explicit_version(IDistroArchSeries, 'beta')
+patch_entry_explicit_version(IEmailAddress, 'beta')
+patch_entry_explicit_version(IGPGKey, 'beta')
+patch_entry_explicit_version(IHasBugs, 'beta')
+patch_entry_explicit_version(IHasMilestones, 'beta')
+patch_entry_explicit_version(IHasTranslationImports, 'beta')
+patch_entry_explicit_version(IHWDBApplication, 'beta')
+patch_entry_explicit_version(IHWDevice, 'beta')
+patch_entry_explicit_version(IHWDeviceClass, 'beta')
+patch_entry_explicit_version(IHWDriver, 'beta')
+patch_entry_explicit_version(IHWDriverName, 'beta')
+patch_entry_explicit_version(IHWDriverPackageName, 'beta')
+patch_entry_explicit_version(IHWSubmission, 'beta')
+patch_entry_explicit_version(IHWSubmissionDevice, 'beta')
+patch_entry_explicit_version(IHWVendorID, 'beta')
+patch_entry_explicit_version(IIrcID, 'beta')
+patch_entry_explicit_version(IJabberID, 'beta')
+patch_entry_explicit_version(ILanguage, 'beta')
+patch_entry_explicit_version(IMessage, 'beta')
+patch_entry_explicit_version(IMilestone, 'beta')
+patch_entry_explicit_version(IPackageset, 'beta')
+patch_entry_explicit_version(IPackageUpload, 'beta')
+patch_entry_explicit_version(IPerson, 'beta')
+patch_entry_explicit_version(IPillar, 'beta')
+patch_entry_explicit_version(IPillarNameSet, 'beta')
+patch_entry_explicit_version(IPOFile, 'beta')
+patch_entry_explicit_version(IPOTemplate, 'beta')
+patch_entry_explicit_version(IPreviewDiff, 'beta')
+patch_entry_explicit_version(IProduct, 'beta')
+patch_entry_explicit_version(IProductRelease, 'beta')
+patch_entry_explicit_version(IProductReleaseFile, 'beta')
+patch_entry_explicit_version(IProductSeries, 'beta')
+patch_entry_explicit_version(IProjectGroup, 'beta')
+patch_entry_explicit_version(ISourcePackage, 'beta')
+patch_entry_explicit_version(ISourcePackagePublishingHistory, 'beta')
+patch_entry_explicit_version(ISourcePackageRecipe, 'beta')
+patch_entry_explicit_version(ISourcePackageRecipeBuild, 'beta')
+patch_entry_explicit_version(ISSHKey, 'beta')
+patch_entry_explicit_version(IStructuralSubscription, 'beta')
+patch_entry_explicit_version(IStructuralSubscriptionTarget, 'beta')
+patch_entry_explicit_version(ITeam, 'beta')
+patch_entry_explicit_version(ITeamMembership, 'beta')
+patch_entry_explicit_version(ITimelineProductSeries, 'beta')
+patch_entry_explicit_version(ITranslationGroup, 'beta')
+patch_entry_explicit_version(ITranslationImportQueueEntry, 'beta')
+patch_entry_explicit_version(IWikiName, 'beta')
+
+patch_operation_explicit_version(IArchive, "_checkUpload", "beta")
+patch_operation_explicit_version(IArchive, "deleteComponentUploader", "beta")
+patch_operation_explicit_version(IArchive, "deletePackageUploader", "beta")
+patch_operation_explicit_version(IArchive, "deletePackagesetUploader", "beta")
+patch_operation_explicit_version(IArchive, "deleteQueueAdmin", "beta")
+patch_operation_explicit_version(IArchive, "getAllPublishedBinaries", "beta")
+patch_operation_explicit_version(IArchive, "getArchiveDependency", "beta")
+patch_operation_explicit_version(IArchive, "getBuildCounters", "beta")
+patch_operation_explicit_version(IArchive, "getBuildSummariesForSourceIds", "beta")
+patch_operation_explicit_version(IArchive, "getComponentsForQueueAdmin", "beta")
+patch_operation_explicit_version(IArchive, "getPackagesetsForSource", "beta")
+patch_operation_explicit_version(IArchive, "getPackagesetsForSourceUploader", "beta")
+patch_operation_explicit_version(IArchive, "getPackagesetsForUploader", "beta")
+patch_operation_explicit_version(IArchive, "getPermissionsForPerson", "beta")
+patch_operation_explicit_version(IArchive, "getPublishedSources", "beta")
+patch_operation_explicit_version(IArchive, "getQueueAdminsForComponent", "beta")
+patch_operation_explicit_version(IArchive, "getUploadersForComponent", "beta")
+patch_operation_explicit_version(IArchive, "getUploadersForPackage", "beta")
+patch_operation_explicit_version(IArchive, "getUploadersForPackageset", "beta")
+patch_operation_explicit_version(IArchive, "isSourceUploadAllowed", "beta")
+patch_operation_explicit_version(IArchive, "newComponentUploader", "beta")
+patch_operation_explicit_version(IArchive, "newPackageUploader", "beta")
+patch_operation_explicit_version(IArchive, "newPackagesetUploader", "beta")
+patch_operation_explicit_version(IArchive, "newQueueAdmin", "beta")
+patch_operation_explicit_version(IArchive, "newSubscription", "beta")
+patch_operation_explicit_version(IArchive, "syncSource", "beta")
+patch_operation_explicit_version(IArchive, "syncSources", "beta")
+patch_operation_explicit_version(IBinaryPackageBuild, "rescore", "beta")
+patch_operation_explicit_version(IBinaryPackageBuild, "retry", "beta")
+patch_operation_explicit_version(IBinaryPackagePublishingHistory, "getDailyDownloadTotals", "beta")
+patch_operation_explicit_version(IBinaryPackagePublishingHistory, "getDownloadCount", "beta")
+patch_operation_explicit_version(IBinaryPackagePublishingHistory, "getDownloadCounts", "beta")
+patch_operation_explicit_version(IBranchMergeProposal, "createComment", "beta")
+patch_operation_explicit_version(IBranchMergeProposal, "getComment", "beta")
+patch_operation_explicit_version(IBranchMergeProposal, "nominateReviewer", "beta")
+patch_operation_explicit_version(IBranchMergeProposal, "setStatus", "beta")
+patch_operation_explicit_version(IBranchMergeProposal, "updatePreviewDiff", "beta")
+patch_operation_explicit_version(IBranchMergeQueue, "setMergeQueueConfig", "beta")
+patch_operation_explicit_version(IBranchSubscription, "canBeUnsubscribedByUser", "beta")
+patch_operation_explicit_version(IBug, "addAttachment", "beta")
+patch_operation_explicit_version(IBug, "addNomination", "beta")
+patch_operation_explicit_version(IBug, "addTask", "beta")
+patch_operation_explicit_version(IBug, "addWatch", "beta")
+patch_operation_explicit_version(IBug, "canBeNominatedFor", "beta")
+patch_operation_explicit_version(IBug, "getHWSubmissions", "beta")
+patch_operation_explicit_version(IBug, "getNominationFor", "beta")
+patch_operation_explicit_version(IBug, "getNominations", "beta")
+patch_operation_explicit_version(IBug, "isExpirable", "beta")
+patch_operation_explicit_version(IBug, "isUserAffected", "beta")
+patch_operation_explicit_version(IBug, "linkCVEAndReturnNothing", "beta")
+patch_operation_explicit_version(IBug, "linkHWSubmission", "beta")
+patch_operation_explicit_version(IBug, "markAsDuplicate", "beta")
+patch_operation_explicit_version(IBug, "markUserAffected", "beta")
+patch_operation_explicit_version(IBug, "newMessage", "beta")
+patch_operation_explicit_version(IBug, "setCommentVisibility", "beta")
+patch_operation_explicit_version(IBug, "setPrivate", "beta")
+patch_operation_explicit_version(IBug, "setSecurityRelated", "beta")
+patch_operation_explicit_version(IBug, "subscribe", "beta")
+patch_operation_explicit_version(IBug, "unlinkCVE", "beta")
+patch_operation_explicit_version(IBug, "unlinkHWSubmission", "beta")
+patch_operation_explicit_version(IBug, "unsubscribe", "beta")
+patch_operation_explicit_version(IBug, "unsubscribeFromDupes", "beta")
+patch_operation_explicit_version(IBugAttachment, "removeFromBug", "beta")
+patch_operation_explicit_version(IBugNomination, "approve", "beta")
+patch_operation_explicit_version(IBugNomination, "canApprove", "beta")
+patch_operation_explicit_version(IBugNomination, "decline", "beta")
+patch_operation_explicit_version(IBugSubscription, "canBeUnsubscribedByUser", "beta")
+patch_operation_explicit_version(IBugSubscriptionFilter, "delete", "beta")
+patch_operation_explicit_version(IBugTask, "findSimilarBugs", "beta")
+patch_operation_explicit_version(IBugTask, "transitionToAssignee", "beta")
+patch_operation_explicit_version(IBugTask, "transitionToImportance", "beta")
+patch_operation_explicit_version(IBugTask, "transitionToMilestone", "beta")
+patch_operation_explicit_version(IBugTask, "transitionToStatus", "beta")
+patch_operation_explicit_version(IBugTask, "transitionToTarget", "beta")
+patch_operation_explicit_version(IBugTracker, "addRemoteComponentGroup", "beta")
+patch_operation_explicit_version(IBugTracker, "getAllRemoteComponentGroups", "beta")
+patch_operation_explicit_version(IBugTracker, "getRemoteComponentGroup", "beta")
+patch_operation_explicit_version(IBugTrackerComponentGroup, "addComponent", "beta")
+patch_operation_explicit_version(IBugTrackerSet, "ensureBugTracker", "beta")
+patch_operation_explicit_version(IBugTrackerSet, "getByName", "beta")
+patch_operation_explicit_version(IBugTrackerSet, "queryByBaseURL", "beta")
+patch_operation_explicit_version(IBuilderSet, "getByName", "beta")
+patch_operation_explicit_version(ICodeImport, "requestImport", "beta")
+patch_operation_explicit_version(ICodeReviewVoteReference, "claimReview", "beta")
+patch_operation_explicit_version(ICodeReviewVoteReference, "delete", "beta")
+patch_operation_explicit_version(ICodeReviewVoteReference, "reassignReview", "beta")
+patch_operation_explicit_version(ICountrySet, "getByCode", "beta")
+patch_operation_explicit_version(ICountrySet, "getByName", "beta")
+patch_operation_explicit_version(IDistribution, "getArchive", "beta")
+patch_operation_explicit_version(IDistribution, "getCommercialPPAs", "beta")
+patch_operation_explicit_version(IDistribution, "getCountryMirror", "beta")
+patch_operation_explicit_version(IDistribution, "getDevelopmentSeries", "beta")
+patch_operation_explicit_version(IDistribution, "getMirrorByName", "beta")
+patch_operation_explicit_version(IDistribution, "getSeries", "beta")
+patch_operation_explicit_version(IDistribution, "getSourcePackage", "beta")
+patch_operation_explicit_version(IDistribution, "searchSourcePackages", "beta")
+patch_operation_explicit_version(IDistributionMirror, "canTransitionToCountryMirror", "beta")
+patch_operation_explicit_version(IDistributionMirror, "getOverallFreshness", "beta")
+patch_operation_explicit_version(IDistributionMirror, "isOfficial", "beta")
+patch_operation_explicit_version(IDistributionMirror, "transitionToCountryMirror", "beta")
+patch_operation_explicit_version(IDistributionSourcePackage, "bugtasks", "beta")
+patch_operation_explicit_version(IDistroSeries, "deriveDistroSeries", "beta")
+patch_operation_explicit_version(IDistroSeries, "getDerivedSeries", "beta")
+patch_operation_explicit_version(IDistroSeries, "getDistroArchSeries", "beta")
+patch_operation_explicit_version(IDistroSeries, "getPackageUploads", "beta")
+patch_operation_explicit_version(IDistroSeries, "getSourcePackage", "beta")
+patch_operation_explicit_version(IDistroSeries, "newMilestone", "beta")
+patch_operation_explicit_version(IDistroSeriesDifference, "addComment", "beta")
+patch_operation_explicit_version(IDistroSeriesDifference, "blacklist", "beta")
+patch_operation_explicit_version(IDistroSeriesDifference, "requestPackageDiffs", "beta")
+patch_operation_explicit_version(IDistroSeriesDifference, "unblacklist", "beta")
+patch_operation_explicit_version(IHWDBApplication, "deviceDriverOwnersAffectedByBugs", "beta")
+patch_operation_explicit_version(IHWDBApplication, "devices", "beta")
+patch_operation_explicit_version(IHWDBApplication, "drivers", "beta")
+patch_operation_explicit_version(IHWDBApplication, "hwInfoByBugRelatedUsers", "beta")
+patch_operation_explicit_version(IHWDBApplication, "numDevicesInSubmissions", "beta")
+patch_operation_explicit_version(IHWDBApplication, "numOwnersOfDevice", "beta")
+patch_operation_explicit_version(IHWDBApplication, "numSubmissionsWithDevice", "beta")
+patch_operation_explicit_version(IHWDBApplication, "vendorIDs", "beta")
+patch_operation_explicit_version(IHWDevice, "getOrCreateDeviceClass", "beta")
+patch_operation_explicit_version(IHWDevice, "getSubmissions", "beta")
+patch_operation_explicit_version(IHWDevice, "removeDeviceClass", "beta")
+patch_operation_explicit_version(IHWDeviceClass, "delete", "beta")
+patch_operation_explicit_version(IHWDriver, "getSubmissions", "beta")
+patch_operation_explicit_version(ILanguageSet, "getAllLanguages", "beta")
+patch_operation_explicit_version(IMaloneApplication, "createBug", "beta")
+patch_operation_explicit_version(IPackageset, "addSources", "beta")
+patch_operation_explicit_version(IPackageset, "addSubsets", "beta")
+patch_operation_explicit_version(IPackageset, "getSourcesIncluded", "beta")
+patch_operation_explicit_version(IPackageset, "getSourcesNotSharedBy", "beta")
+patch_operation_explicit_version(IPackageset, "getSourcesSharedBy", "beta")
+patch_operation_explicit_version(IPackageset, "relatedSets", "beta")
+patch_operation_explicit_version(IPackageset, "removeSources", "beta")
+patch_operation_explicit_version(IPackageset, "removeSubsets", "beta")
+patch_operation_explicit_version(IPackageset, "setsIncluded", "beta")
+patch_operation_explicit_version(IPackageset, "setsIncludedBy", "beta")
+patch_operation_explicit_version(IPackagesetSet, "getByName", "beta")
+patch_operation_explicit_version(IPackagesetSet, "new", "beta")
+patch_operation_explicit_version(IPackagesetSet, "setsIncludingSource", "beta")
+patch_operation_explicit_version(IPillarNameSet, "search", "beta")
+patch_operation_explicit_version(IProduct, "getRelease", "beta")
+patch_operation_explicit_version(IProduct, "getSeries", "beta")
+patch_operation_explicit_version(IProduct, "getTimeline", "beta")
+patch_operation_explicit_version(IProduct, "newSeries", "beta")
+patch_operation_explicit_version(IProductRelease, "destroySelf", "beta")
+patch_operation_explicit_version(IProductRelease, "addReleaseFile", "beta")
+patch_operation_explicit_version(IProductSeries, "getTimeline", "beta")
+patch_operation_explicit_version(IProductSeries, "newMilestone", "beta")
+patch_operation_explicit_version(IProductReleaseFile, "destroySelf", "beta")
+patch_operation_explicit_version(IProductSet, "createProduct", "beta")
+patch_operation_explicit_version(IProductSet, "forReview", "beta")
+patch_operation_explicit_version(IProductSet, "latest", "beta")
+patch_operation_explicit_version(IProductSet, "search", "beta")
+patch_operation_explicit_version(IProjectGroupSet, "search", "beta")
+patch_operation_explicit_version(ISourcePackage, "getBranch", "beta")
+patch_operation_explicit_version(ISourcePackage, "linkedBranches", "beta")
+patch_operation_explicit_version(ISourcePackage, "setBranch", "beta")
+patch_operation_explicit_version(ISourcePackagePublishingHistory, "api_requestDeletion", "beta")
+patch_operation_explicit_version(ISourcePackagePublishingHistory, "binaryFileUrls", "beta")
+patch_operation_explicit_version(ISourcePackagePublishingHistory, "changesFileUrl", "beta")
+patch_operation_explicit_version(ISourcePackagePublishingHistory, "getBuilds", "beta")
+patch_operation_explicit_version(ISourcePackagePublishingHistory, "getPublishedBinaries", "beta")
+patch_operation_explicit_version(ISourcePackagePublishingHistory, "packageDiffUrl", "beta")
+patch_operation_explicit_version(ISourcePackagePublishingHistory, "sourceFileUrls", "beta")
+patch_operation_explicit_version(ISourcePackageRecipe, "performDailyBuild", "beta")
+patch_operation_explicit_version(ISourcePackageRecipe, "requestBuild", "beta")
+patch_operation_explicit_version(ISourcePackageRecipe, "setRecipeText", "beta")
+patch_operation_explicit_version(IStructuralSubscription, "delete", "beta")
+patch_operation_explicit_version(IStructuralSubscription, "newBugFilter", "beta")
+patch_operation_explicit_version(ITeamMembership, "setExpirationDate", "beta")
+patch_operation_explicit_version(ITeamMembership, "setStatus", "beta")
+patch_operation_explicit_version(ITemporaryBlobStorage, "getProcessedData", "beta")
+patch_operation_explicit_version(ITemporaryBlobStorage, "hasBeenProcessed", "beta")
+patch_operation_explicit_version(ITemporaryStorageManager, "fetch", "beta")
+patch_operation_explicit_version(ITranslationImportQueue, "getAllEntries", "beta")
+patch_operation_explicit_version(ITranslationImportQueue, "getFirstEntryToImport", "beta")
+patch_operation_explicit_version(ITranslationImportQueue, "getRequestTargets", "beta")
+patch_operation_explicit_version(ITranslationImportQueueEntry, "setStatus", "beta")

=== modified file 'lib/canonical/launchpad/interfaces/temporaryblobstorage.py'
--- lib/canonical/launchpad/interfaces/temporaryblobstorage.py	2011-03-09 04:49:00 +0000
+++ lib/canonical/launchpad/interfaces/temporaryblobstorage.py	2011-03-23 16:18:44 +0000
@@ -47,11 +47,12 @@
 class ITemporaryBlobStorage(Interface):
     """A blob which we will store in the database temporarily."""
     export_as_webservice_entry(
-        singular_name='temporary_blob', plural_name='temporary_blobs')
+        singular_name='temporary_blob', plural_name='temporary_blobs',
+        as_of="beta")
 
     uuid = exported(
         Text(title=_('UUID'), required=True, readonly=True),
-        exported_as='token')
+        exported_as='token', as_of="beta")
     blob = Bytes(title=_('BLOB'), required=True, readonly=True)
     date_created = Datetime(title=_('Date created'),
         required=True, readonly=True)

=== modified file 'lib/canonical/launchpad/rest/configuration.py'
--- lib/canonical/launchpad/rest/configuration.py	2010-12-13 18:04:24 +0000
+++ lib/canonical/launchpad/rest/configuration.py	2011-03-23 16:18:44 +0000
@@ -27,6 +27,7 @@
     last_version_with_mutator_named_operations = "beta"
     first_version_with_total_size_link = "devel"
     view_permission = "launchpad.View"
+    require_explicit_versions = True
     compensate_for_mod_compress_etag_modification = True
 
     service_description = """The Launchpad web service allows automated

=== modified file 'lib/lp/app/interfaces/launchpad.py'
--- lib/lp/app/interfaces/launchpad.py	2011-03-07 19:05:44 +0000
+++ lib/lp/app/interfaces/launchpad.py	2011-03-23 16:18:44 +0000
@@ -50,10 +50,7 @@
         title=_('Type of service for translations application'),
         description=_("Where does this pillar do translations?"),
         default=ServiceUsage.UNKNOWN,
-        vocabulary=ServiceUsage),
-        ('devel', {'exported': True}),
-        exported=False,
-        )
+        vocabulary=ServiceUsage), as_of="devel")
     bug_tracking_usage = Choice(
         title=_('Type of service for tracking bugs'),
         description=_("Where does this pillar track bugs?"),

=== modified file 'lib/lp/blueprints/interfaces/specification.py'
--- lib/lp/blueprints/interfaces/specification.py	2011-03-08 22:45:14 +0000
+++ lib/lp/blueprints/interfaces/specification.py	2011-03-23 16:18:44 +0000
@@ -144,28 +144,29 @@
                 "May contain lower-case letters, numbers, and dashes. "
                 "It will be used in the specification url. "
                 "Examples: mozilla-type-ahead-find, postgres-smart-serial.")),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     title = exported(
         Title(
             title=_('Title'), required=True, description=_(
                 "Describe the feature as clearly as possible in up to 70 "
                 "characters. This title is displayed in every feature "
                 "list or report.")),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     specurl = exported(
         SpecURLField(
             title=_('Specification URL'), required=False,
             description=_(
                 "The URL of the specification. This is usually a wiki page."),
             constraint=valid_webref),
-        ('devel', dict(exported=True, exported_as='specification_url')),
-        exported=False)
+        exported_as="specification_url",
+        as_of="devel",
+        )
     summary = exported(
         Summary(
             title=_('Summary'), required=True, description=_(
                 "A single-paragraph description of the feature. "
                 "This will also be displayed in most feature listings.")),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     definition_status = exported(
         Choice(
@@ -175,7 +176,7 @@
             description=_(
                 "The current status of the process to define the "
                 "feature and get approval for the implementation plan.")),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     assignee = exported(
         PublicPersonChoice(
@@ -183,14 +184,14 @@
             description=_(
                 "The person responsible for implementing the feature."),
             vocabulary='ValidPersonOrTeam'),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     drafter = exported(
         PublicPersonChoice(
             title=_('Drafter'), required=False,
             description=_(
                     "The person responsible for drafting the specification."),
                 vocabulary='ValidPersonOrTeam'),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     approver = exported(
         PublicPersonChoice(
             title=_('Approver'), required=False,
@@ -198,23 +199,24 @@
                 "The person responsible for approving the specification, "
                 "and for reviewing the code when it's ready to be landed."),
             vocabulary='ValidPersonOrTeam'),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     priority = exported(
         Choice(
             title=_('Priority'), vocabulary=SpecificationPriority,
             default=SpecificationPriority.UNDEFINED, required=True),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     datecreated = exported(
         Datetime(
             title=_('Date Created'), required=True, readonly=True),
-        ('devel', dict(exported=True, exported_as='date_created')),
-        exported=False)
+        as_of="devel",
+        exported_as="date_created",
+        )
     owner = exported(
         PublicPersonChoice(
             title=_('Owner'), required=True, readonly=True,
             vocabulary='ValidPersonOrTeam'),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     product = Choice(title=_('Project'), required=False,
                      vocabulary='Product')
@@ -229,7 +231,9 @@
             description=_(
                 "The project for which this proposal is being made."),
             schema=ISpecificationTarget),
-        ('devel', dict(exported=True, readonly=True)), exported=False)
+        as_of="devel",
+        readonly=True,
+        )
 
     productseries = Choice(
         title=_('Series Goal'), required=False,
@@ -252,7 +256,7 @@
                 "The milestone in which we would like this feature to be "
                 "delivered."),
             schema=IMilestone),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     # nomination to a series for release management
     # XXX: It'd be nice to export goal as read-only, but it's tricky because
@@ -277,7 +281,7 @@
              description=_(
                 "Any notes on the status of this spec you would like to "
                 "make. Your changes will override the current text.")),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     direction_approved = exported(
         Bool(title=_('Basic direction approved?'),
              required=True, default=False,
@@ -285,7 +289,7 @@
                 "Check this to indicate that the drafter and assignee "
                 "have satisfied the approver that they are headed in "
                 "the right basic direction with this specification.")),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     man_days = Int(title=_("Estimated Developer Days"),
         required=False, default=None, description=_("An estimate of the "
         "number of developer days it will take to implement this feature. "
@@ -299,7 +303,7 @@
             description=_(
                 "The state of progress being made on the actual "
                 "implementation or delivery of this feature.")),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     superseded_by = Choice(title=_("Superseded by"),
         required=False, default=None,
         vocabulary='Specification', description=_("The specification "
@@ -315,12 +319,12 @@
                 'The person who first set the state of the '
                 'spec to the values that we consider mark it as started.'),
             vocabulary='ValidPersonOrTeam'),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     date_started = exported(
         Datetime(
             title=_('Date Started'), required=False, readonly=True,
             description=_('The date when this spec was marked started.')),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     completer = exported(
         PublicPersonChoice(
@@ -329,7 +333,7 @@
             'The person who finally set the state of the '
             'spec to the values that we consider mark it as complete.'),
             vocabulary='ValidPersonOrTeam'),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     date_completed = exported(
         Datetime(
@@ -339,7 +343,7 @@
                 'complete. Note that complete also includes "obsolete" and '
                 'superseded. Essentially, it is the state where no more work '
                 'will be done on the feature.')),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     # joins
     subscriptions = Attribute('The set of subscriptions to this spec.')
@@ -352,7 +356,7 @@
             title=_('Specs on which this one depends.'),
             value_type=Reference(schema=Interface),  # ISpecification, really.
             readonly=True),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     blocked_specs = Attribute('Specs for which this spec is a dependency.')
     all_deps = Attribute(
         "All the dependencies, including dependencies of dependencies.")
@@ -364,7 +368,7 @@
             "branches on which this spec is being implemented."),
             value_type=Reference(schema=Interface), # ISpecificationBranch
             readonly=True),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     # emergent properties
     informational = Attribute('Is True if this spec is purely informational '
@@ -379,7 +383,7 @@
                 'code to be written. It is also true of obsolete and '
                 'superseded specs, since there is no longer any need '
                 'to schedule work for them.')),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     is_incomplete = Attribute('Is True if this work still needs to '
         'be done. Is in fact always the opposite of is_complete.')
@@ -393,7 +397,7 @@
                 'we consider to be "started". This looks at the delivery '
                 'attribute, and also considers informational specs to be '
                 'started when they are approved.')),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     lifecycle_status = exported(
         Choice(
@@ -401,7 +405,7 @@
             vocabulary=SpecificationLifecycleStatus,
             default=SpecificationLifecycleStatus.NOTSTARTED,
             readonly=True),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     def validateMove(target):
         """Check that the specification can be moved to the target."""
@@ -548,7 +552,7 @@
                      IBugLinkTarget):
     """A Specification."""
 
-    export_as_webservice_entry()
+    export_as_webservice_entry(as_of="devel")
 
 
 class ISpecificationSet(IHasSpecifications):

=== modified file 'lib/lp/blueprints/interfaces/specificationbranch.py'
--- lib/lp/blueprints/interfaces/specificationbranch.py	2010-09-26 22:47:58 +0000
+++ lib/lp/blueprints/interfaces/specificationbranch.py	2011-03-23 16:18:44 +0000
@@ -17,6 +17,7 @@
     export_operation_as,
     export_write_operation,
     exported,
+    operation_for_version,
     )
 from lazr.restful.fields import (
     Reference,
@@ -35,28 +36,30 @@
 class ISpecificationBranch(IHasDateCreated):
     """A branch linked to a specification."""
 
-    export_as_webservice_entry()
+    export_as_webservice_entry(as_of="devel")
 
     id = Int(title=_("Specification Branch #"))
     specification = exported(
         ReferenceChoice(
             title=_("Blueprint"), vocabulary="Specification",
             required=True,
-            readonly=True, schema=ISpecification))
+            readonly=True, schema=ISpecification), as_of="devel")
     branch = exported(
         ReferenceChoice(
             title=_("Branch"),
             vocabulary="Branch",
             required=True,
-            schema=IBranch))
+            schema=IBranch), as_of="devel")
 
     registrant = exported(
         Reference(
             schema=IPerson, readonly=True, required=True,
-            title=_("The person who linked the bug to the branch")))
+            title=_("The person who linked the bug to the branch")),
+        as_of="devel")
 
     @export_operation_as('delete')
     @export_write_operation()
+    @operation_for_version('devel')
     def destroySelf():
         """Destroy this specification branch link"""
 

=== modified file 'lib/lp/blueprints/interfaces/specificationtarget.py'
--- lib/lp/blueprints/interfaces/specificationtarget.py	2011-01-11 19:37:03 +0000
+++ lib/lp/blueprints/interfaces/specificationtarget.py	2011-03-23 16:18:44 +0000
@@ -51,7 +51,7 @@
             description=_(
                 'A list of all specifications, regardless of status or '
                 'approval or completion, for this object.'))),
-        ('devel', dict(exported=True)), exported=False)
+                                  as_of="devel")
 
     has_any_specifications = Attribute(
         'A true or false indicator of whether or not this object has any '
@@ -66,7 +66,7 @@
                 'All specifications that are not obsolete. When called from '
                 'an ISpecificationGoal it will also exclude the ones that '
                 'have not been accepted for that goal'))),
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
 
     latest_specifications = Attribute(
         "The latest 5 specifications registered for this context.")
@@ -99,7 +99,7 @@
     specifications directly attached to them.
     """
 
-    export_as_webservice_entry()
+    export_as_webservice_entry(as_of="devel")
 
     @operation_parameters(
         name=TextLine(title=_('The name of the specification')))

=== modified file 'lib/lp/bugs/browser/bug.py'
--- lib/lp/bugs/browser/bug.py	2011-03-21 11:39:26 +0000
+++ lib/lp/bugs/browser/bug.py	2011-03-23 16:18:44 +0000
@@ -284,7 +284,11 @@
             link = "+mute"
 
         return Link(
+<<<<<<< TREE
             link, text, icon='remove', summary=(
+=======
+            '+mute', text, icon='remove', summary=(
+>>>>>>> MERGE-SOURCE
                 "Mute this bug so that you will never receive emails "
                 "about it."))
 

=== modified file 'lib/lp/bugs/browser/bugalsoaffects.py'
=== modified file 'lib/lp/bugs/browser/bugtask.py'
=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
--- lib/lp/bugs/browser/tests/test_bugtask.py	2011-03-21 21:36:55 +0000
+++ lib/lp/bugs/browser/tests/test_bugtask.py	2011-03-23 16:18:44 +0000
@@ -545,6 +545,7 @@
             view.form_fields['assignee'].field.vocabularyName)
 
 
+<<<<<<< TREE
 class TestBugTaskEditView(TestCaseWithFactory):
     """Test the bug task edit form."""
 
@@ -642,6 +643,46 @@
         self.assertTrue(notifications.pop().message.startswith(expected))
 
 
+=======
+class TestBugTaskEditView(TestCaseWithFactory):
+    """Test the bugs overview page for Project Groups."""
+
+    layer = DatabaseFunctionalLayer
+
+    def test_retarget_already_exists_error(self):
+        user = self.factory.makePerson()
+        login_person(user)
+        ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
+        dsp_1 = self.factory.makeDistributionSourcePackage(
+            distribution=ubuntu, sourcepackagename='mouse')
+        ignore = self.factory.makeSourcePackagePublishingHistory(
+            distroseries=ubuntu.currentseries,
+            sourcepackagename=dsp_1.sourcepackagename)
+        bug_task_1 = self.factory.makeBugTask(target=dsp_1)
+        dsp_2 = self.factory.makeDistributionSourcePackage(
+            distribution=ubuntu, sourcepackagename='rabbit')
+        ignore = self.factory.makeSourcePackagePublishingHistory(
+            distroseries=ubuntu.currentseries,
+            sourcepackagename=dsp_2.sourcepackagename)
+        bug_task_2 = self.factory.makeBugTask(
+            bug=bug_task_1.bug, target=dsp_2)
+        form = {
+            'ubuntu_rabbit.actions.save': 'Save Changes',
+            'ubuntu_rabbit.status': 'In Progress',
+            'ubuntu_rabbit.importance': 'High',
+            'ubuntu_rabbit.assignee.option':
+                'ubuntu_rabbit.assignee.assign_to_nobody',
+            'ubuntu_rabbit.sourcepackagename': 'mouse',
+            }
+        view = create_initialized_view(
+            bug_task_2, name='+editstatus', form=form, principal=user)
+        self.assertEqual(1, len(view.errors))
+        self.assertEqual(
+            'This bug has already been reported on mouse (ubuntu).',
+            view.errors[0])
+
+
+>>>>>>> MERGE-SOURCE
 class TestProjectGroupBugs(TestCaseWithFactory):
     """Test the bugs overview page for Project Groups."""
 

=== modified file 'lib/lp/bugs/interfaces/buglink.py'
--- lib/lp/bugs/interfaces/buglink.py	2010-12-02 15:13:58 +0000
+++ lib/lp/bugs/interfaces/buglink.py	2011-03-23 16:18:44 +0000
@@ -60,11 +60,11 @@
 
     Examples include an ISpecification.
     """
-    export_as_webservice_entry()
+    export_as_webservice_entry(as_of="beta")
 
     bugs = exported(CollectionField(title=_("Bugs related to this object."),
                 value_type=Reference(schema=IBug), readonly=True),
-                ('devel', dict(exported=True)), exported=False)
+                    as_of="devel")
     bug_links = List(title=_("The links between bugs and this object."),
                      value_type=Object(schema=IBugLink), readonly=True)
 

=== modified file 'lib/lp/bugs/interfaces/bugsubscription.py'
--- lib/lp/bugs/interfaces/bugsubscription.py	2011-02-04 06:30:13 +0000
+++ lib/lp/bugs/interfaces/bugsubscription.py	2011-03-23 16:18:44 +0000
@@ -39,15 +39,15 @@
 class IBugSubscription(Interface):
     """The relationship between a person and a bug."""
 
-    export_as_webservice_entry(publish_web_link=False)
+    export_as_webservice_entry(publish_web_link=False, as_of="beta")
 
     id = Int(title=_('ID'), readonly=True, required=True)
     person = exported(PersonChoice(
         title=_('Person'), required=True, vocabulary='ValidPersonOrTeam',
         readonly=True, description=_("The person's Launchpad ID or "
-        "e-mail address.")))
+        "e-mail address.")), as_of="beta")
     bug = exported(Reference(
-        IBug, title=_("Bug"), required=True, readonly=True))
+        IBug, title=_("Bug"), required=True, readonly=True), as_of="beta")
     # We mark this as doNotSnapshot() because it's a magically-generated
     # Storm attribute and it causes Snapshot to break.
     bugID = doNotSnapshot(Int(title=u"The bug id.", readonly=True))
@@ -60,15 +60,15 @@
                 "The volume and type of bug notifications "
                 "this subscription will generate."),
             ),
-        # We want this field to be exported in the devel version of the
-        # API only.
-        ('devel', dict(exported=True)), exported=False)
+        as_of="devel")
     date_created = exported(
-        Datetime(title=_('Date subscribed'), required=True, readonly=True))
+        Datetime(title=_('Date subscribed'), required=True, readonly=True),
+        as_of="beta")
     subscribed_by = exported(PersonChoice(
         title=_('Subscribed by'), required=True,
         vocabulary='ValidPersonOrTeam', readonly=True,
-        description=_("The person who created this subscription.")))
+        description=_("The person who created this subscription.")),
+                             as_of="beta")
 
     display_subscribed_by = Attribute(
         "`subscribed_by` formatted for display.")

=== modified file 'lib/lp/bugs/interfaces/bugsupervisor.py'
--- lib/lp/bugs/interfaces/bugsupervisor.py	2011-01-25 21:30:49 +0000
+++ lib/lp/bugs/interfaces/bugsupervisor.py	2011-03-23 16:18:44 +0000
@@ -16,6 +16,7 @@
     export_write_operation,
     exported,
     mutator_for,
+    operation_for_version,
     operation_parameters,
     REQUEST_USER,
     )
@@ -43,5 +44,6 @@
     @operation_parameters(
         bug_supervisor=copy_field(bug_supervisor))
     @export_write_operation()
+    @operation_for_version('beta')
     def setBugSupervisor(bug_supervisor, user):
         """Set the bug contact and create a bug subscription."""

=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
--- lib/lp/bugs/interfaces/bugtarget.py	2011-01-31 13:05:20 +0000
+++ lib/lp/bugs/interfaces/bugtarget.py	2011-03-23 16:18:44 +0000
@@ -28,6 +28,7 @@
     export_write_operation,
     exported,
     LAZR_WEBSERVICE_EXPORTED,
+    operation_for_version,
     operation_parameters,
     operation_removed_in_version,
     operation_returns_collection_of,
@@ -229,7 +230,7 @@
     @operation_parameters(**search_tasks_params_for_api_default)
     @operation_returns_collection_of(IBugTask)
     @export_read_operation()
-
+    @operation_for_version('beta')
     def searchTasks(search_params, user=None,
                     order_by=None, search_text=None,
                     status=None, importance=None,
@@ -417,12 +418,14 @@
     @operation_parameters(
         tag=Tag(title=u'The official bug tag', required=True))
     @export_write_operation()
+    @operation_for_version('beta')
     def addOfficialBugTag(tag):
         """Add tag to the official bug tags of this target."""
 
     @operation_parameters(
         tag=Tag(title=u'The official bug tag', required=True))
     @export_write_operation()
+    @operation_for_version('beta')
     def removeOfficialBugTag(tag):
         """Remove tag from the official bug tags of this target."""
 

=== modified file 'lib/lp/bugs/interfaces/structuralsubscription.py'
--- lib/lp/bugs/interfaces/structuralsubscription.py	2011-03-07 15:39:27 +0000
+++ lib/lp/bugs/interfaces/structuralsubscription.py	2011-03-23 16:18:44 +0000
@@ -22,6 +22,7 @@
     export_read_operation,
     export_write_operation,
     exported,
+    operation_for_version,
     operation_parameters,
     operation_returns_collection_of,
     operation_returns_entry,
@@ -115,6 +116,7 @@
 
     @operation_returns_collection_of(IStructuralSubscription)
     @export_read_operation()
+    @operation_for_version('beta')
     def getSubscriptions():
         """Return all the subscriptions with the specified levels.
 
@@ -136,6 +138,7 @@
     @operation_parameters(person=Reference(schema=IPerson))
     @operation_returns_entry(IStructuralSubscription)
     @export_read_operation()
+    @operation_for_version('beta')
     def getSubscription(person):
         """Return the subscription for `person`, if it exists."""
 
@@ -172,6 +175,7 @@
             required=False))
     @call_with(subscribed_by=REQUEST_USER)
     @export_factory_operation(IStructuralSubscription, [])
+    @operation_for_version('beta')
     def addBugSubscription(subscriber, subscribed_by):
         """Add a bug subscription for this structure.
 
@@ -194,6 +198,7 @@
             required=False))
     @call_with(unsubscribed_by=REQUEST_USER)
     @export_write_operation()
+    @operation_for_version('beta')
     def removeBugSubscription(subscriber, unsubscribed_by):
         """Remove a subscription to bugs from this structure.
 

=== modified file 'lib/lp/bugs/javascript/bugtask_index_portlets.js'
--- lib/lp/bugs/javascript/bugtask_index_portlets.js	2011-03-21 11:42:33 +0000
+++ lib/lp/bugs/javascript/bugtask_index_portlets.js	2011-03-23 16:18:44 +0000
@@ -285,9 +285,15 @@
     mute_link.addClass('js-action');
     mute_link.on('click', function(e) {
         e.halt();
+<<<<<<< TREE
         var is_muted = parent_node.hasClass('muted-true');
         if (! is_muted) {
             mute_subscription.enable_spinner('Muting...');
+=======
+        var is_muted = parent_node.hasClass('muted-true');
+        mute_subscription.enable_spinner('Muting...');
+        if (! is_muted) {
+>>>>>>> MERGE-SOURCE
             mute_current_user(mute_subscription);
         } else {
             var unmute_overlay = setup_advanced_subscription_overlay(
@@ -298,6 +304,7 @@
     });
 }
 
+<<<<<<< TREE
 /*
  * Update the Mute link after the user's subscriptions or mutes have
  * changed.
@@ -321,6 +328,22 @@
         mute_subscription.disable_spinner("Mute bug mail");
     }
 }
+=======
+/*
+ * Update the Mute link after the user's subscriptions or mutes have
+ * changed.
+ */
+function update_mute_after_subscription_change(mute_subscription) {
+    var parent_node = mute_subscription.get('link').get('parentNode');
+    if (mute_subscription.get('is_muted')) {
+        parent_node.removeClass('muted-false');
+        parent_node.addClass('muted-true');
+    } else {
+        parent_node.removeClass('muted-true');
+        parent_node.addClass('muted-false');
+    }
+}
+>>>>>>> MERGE-SOURCE
 
 /*
  * Update the subscription links after the mute button has been clicked.
@@ -368,6 +391,7 @@
         'link').get('parentNode').hasClass('dup-subscribed-true');
     subscription.set('is_direct', is_direct);
     subscription.set('has_dupes', has_dupes);
+<<<<<<< TREE
     subscription.set('can_be_unsubscribed', true);
     subscription.set('person', subscription.get('subscriber'));
     subscription.set('is_team', false);
@@ -385,6 +409,22 @@
     }
 
     var subscription = get_subscribe_self_subscription();
+=======
+    return subscription;
+}
+
+/*
+ * Initialize callbacks for subscribe/unsubscribe links.
+ *
+ * @method setup_subscription_link_handlers
+ */
+function setup_subscription_link_handlers() {
+    if (LP.links.me === undefined) {
+        return;
+    }
+
+    var subscription = get_subscribe_self_subscription();
+>>>>>>> MERGE-SOURCE
     var subscription_overlay;
     if (namespace.use_advanced_subscriptions) {
         subscription_overlay = setup_advanced_subscription_overlay(
@@ -648,8 +688,13 @@
         'link').get('href') + '/++form++';
     subscription_overlay.set(
         'form_submit_callback', function(form_data) {
+<<<<<<< TREE
         handle_advanced_subscription_overlay(form_data);
         Y.lp.bugs.bug_subscription.clean_up_level_div();
+=======
+        handle_advanced_subscription_overlay(subscription, form_data);
+        Y.lp.bugs.bug_subscription.clean_up_level_div();
+>>>>>>> MERGE-SOURCE
         subscription_overlay.hide();
     });
 

=== modified file 'lib/lp/bugs/model/bug.py'
=== modified file 'lib/lp/bugs/model/bugtask.py'
=== modified file 'lib/lp/buildmaster/interfaces/buildfarmjob.py'
--- lib/lp/buildmaster/interfaces/buildfarmjob.py	2010-10-04 21:44:32 +0000
+++ lib/lp/buildmaster/interfaces/buildfarmjob.py	2011-03-23 16:18:44 +0000
@@ -172,7 +172,9 @@
             title=_("Date created"), required=True, readonly=True,
             description=_(
                 "The timestamp when the build farm job was created.")),
-        ("1.0", dict(exported=True, exported_as="datecreated")))
+        ("1.0", dict(exported_as="datecreated")),
+        as_of="beta",
+        )
 
     date_started = Datetime(
         title=_("Date started"), required=False, readonly=True,
@@ -183,7 +185,9 @@
             title=_("Date finished"), required=False, readonly=True,
             description=_(
                 "The timestamp when the build farm job was finished.")),
-        ("1.0", dict(exported=True, exported_as="datebuilt")))
+        ("1.0", dict(exported_as="datebuilt")),
+        as_of="beta",
+        )
 
     duration = Timedelta(
         title=_("Duration"), required=False,
@@ -214,7 +218,9 @@
             # _schema_circular_imports.py
             vocabulary=DBEnumeratedType,
             description=_("The current status of the job.")),
-        ("1.0", dict(exported=True, exported_as="buildstate")))
+        ("1.0", dict(exported_as="buildstate")),
+        as_of="beta",
+        )
 
     log = Reference(
         schema=ILibraryFileAlias, required=False,
@@ -226,7 +232,9 @@
             title=_("Build Log URL"), required=False,
             description=_("A URL for the build log. None if there is no "
                           "log available.")),
-        ("1.0", dict(exported=True, exported_as="build_log_url")))
+        ("1.0", dict(exported_as="build_log_url")),
+        as_of="beta",
+        )
 
     is_private = Bool(
         title=_("is private"), required=False, readonly=True,
@@ -252,7 +260,8 @@
     def gotFailure():
         """Increment the failure_count for this job."""
 
-    title = exported(TextLine(title=_("Title"), required=False))
+    title = exported(TextLine(title=_("Title"), required=False),
+                     as_of="beta")
 
     was_built = Attribute("Whether or not modified by the builddfarm.")
 
@@ -263,7 +272,8 @@
             title=_('Dependencies'), required=False,
             description=_(
                 'Debian-like dependency line that must be satisfied before '
-                'attempting to build this request.')))
+                'attempting to build this request.')),
+        as_of="beta")
 
 
 class ISpecificBuildFarmJob(IBuildFarmJob):

=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py	2011-03-21 21:30:07 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py	2011-03-23 16:18:44 +0000
@@ -785,6 +785,7 @@
         self.assertThat(
             'PPA 2', MatchesPickerText(content, 'edit-daily_build_archive'))
 
+<<<<<<< TREE
     def test_edit_recipe_sets_date_last_modified(self):
         """Editing a recipe sets the date_last_modified property."""
         date_created = datetime(2000, 1, 1, 12, tzinfo=UTC)
@@ -801,6 +802,24 @@
         self.assertSqlAttributeEqualsDate(
             recipe, 'date_last_modified', UTC_NOW)
 
+=======
+    def test_edit_recipe_sets_date_last_modified(self):
+        """Editing a recipe sets the date_last_modified property."""
+        date_created = datetime(2000, 1, 1, 12, tzinfo=UTC)
+        recipe = self.factory.makeSourcePackageRecipe(
+            owner=self.chef, date_created=date_created)
+
+        login_person(self.chef)
+        view = SourcePackageRecipeEditView(recipe, LaunchpadTestRequest())
+        view.initialize()
+        view.request_action.success({
+            'name': u'fings',
+            'recipe_text': recipe.recipe_text,
+            'distros': recipe.distroseries})
+        self.assertSqlAttributeEqualsDate(
+            recipe, 'date_last_modified', UTC_NOW)
+
+>>>>>>> MERGE-SOURCE
     def test_admin_edit(self):
         self.factory.makeDistroSeries(
             displayname='Mumbly Midget', name='mumbly',

=== modified file 'lib/lp/code/configure.zcml'
=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py	2011-03-22 00:18:47 +0000
+++ lib/lp/code/interfaces/branch.py	2011-03-23 16:18:44 +0000
@@ -38,6 +38,7 @@
     export_write_operation,
     exported,
     mutator_for,
+    operation_for_version,
     operation_parameters,
     operation_returns_collection_of,
     operation_returns_entry,
@@ -261,6 +262,7 @@
     @operation_parameters(
         scheme=TextLine(title=_("URL scheme"), default=u'http'))
     @export_read_operation()
+    @operation_for_version('beta')
     def composePublicURL(scheme='http'):
         """Return a public URL for the branch using the given protocol.
 
@@ -348,6 +350,7 @@
             title=_("A person for which the reviewer status is in question."),
             schema=IPerson))
     @export_read_operation()
+    @operation_for_version('beta')
     def isPersonTrustedReviewer(reviewer):
         """Return true if the `reviewer` is a trusted reviewer.
 
@@ -424,6 +427,7 @@
     @operation_parameters(
         bug=Reference(schema=Interface)) # Really IBug
     @export_write_operation()
+    @operation_for_version('beta')
     def linkBug(bug, registrant):
         """Link a bug to this branch.
 
@@ -435,6 +439,7 @@
     @operation_parameters(
         bug=Reference(schema=Interface)) # Really IBug
     @export_write_operation()
+    @operation_for_version('beta')
     def unlinkBug(bug, user):
         """Unlink a bug to this branch.
 
@@ -447,12 +452,14 @@
         CollectionField(
             title=_("Specification linked to this branch."),
             readonly=True,
-            value_type=Reference(Interface))) # Really ISpecificationBranch
+            value_type=Reference(Interface)), # Really ISpecificationBranch
+        as_of="devel")
 
     @call_with(registrant=REQUEST_USER)
     @operation_parameters(
         spec=Reference(schema=Interface)) # Really ISpecification
     @export_write_operation()
+    @operation_for_version('devel')
     def linkSpecification(spec, registrant):
         """Link an ISpecification to a branch.
 
@@ -464,6 +471,7 @@
     @operation_parameters(
         spec=Reference(schema=Interface)) # Really ISpecification
     @export_write_operation()
+    @operation_for_version('devel')
     def unlinkSpecification(spec, user):
         """Unlink an ISpecification to a branch.
 
@@ -557,6 +565,7 @@
     @call_with(registrant=REQUEST_USER)
     # IBranchMergeProposal supplied as Interface to avoid circular imports.
     @export_factory_operation(Interface, [])
+    @operation_for_version('beta')
     def _createMergeProposal(
         registrant, target_branch, prerequisite_branch=None,
         needs_review=True, initial_comment=None, commit_message=None,
@@ -604,6 +613,7 @@
     @call_with(visible_by_user=REQUEST_USER)
     @operation_returns_collection_of(Interface) # Really IBranchMergeProposal.
     @export_read_operation()
+    @operation_for_version('beta')
     def getMergeProposals(status=None, visible_by_user=None,
                           merged_revnos=None):
         """Return matching BranchMergeProposals."""
@@ -672,6 +682,7 @@
         """
 
     @export_read_operation()
+    @operation_for_version('beta')
     def canBeDeleted():
         """Can this branch be deleted in its current state.
 
@@ -760,6 +771,7 @@
     @operation_returns_entry(Interface) # Really IBranchSubscription
     @call_with(subscribed_by=REQUEST_USER)
     @export_write_operation()
+    @operation_for_version('beta')
     def subscribe(person, notification_level, max_diff_lines,
                   code_review_level, subscribed_by):
         """Subscribe this person to the branch.
@@ -781,6 +793,7 @@
             schema=IPerson))
     @operation_returns_entry(Interface) # Really IBranchSubscription
     @export_read_operation()
+    @operation_for_version('beta')
     def getSubscription(person):
         """Return the BranchSubscription for this person."""
 
@@ -793,6 +806,7 @@
             schema=IPerson))
     @call_with(unsubscribed_by=REQUEST_USER)
     @export_write_operation()
+    @operation_for_version('beta')
     def unsubscribe(person, unsubscribed_by):
         """Remove the person's subscription to this branch.
 
@@ -899,6 +913,7 @@
         """Return the URL used to pull the branch into the mirror area."""
 
     @export_write_operation()
+    @operation_for_version('beta')
     def requestMirror():
         """Request that this branch be mirrored on the next run of the branch
         puller.
@@ -1026,6 +1041,7 @@
             title=_("The new owner of the branch."),
             schema=IPerson))
     @export_write_operation()
+    @operation_for_version('beta')
     def setOwner(new_owner, user):
         """Set the owner of the branch to be `new_owner`."""
 
@@ -1038,6 +1054,7 @@
             title=_("The source package the branch belongs to."),
             schema=Interface, required=False)) # Really ISourcePackage
     @export_write_operation()
+    @operation_for_version('beta')
     def setTarget(user, project=None, source_package=None):
         """Set the target of the branch to be `project` or `source_package`.
 
@@ -1071,6 +1088,7 @@
         """
 
     @export_destructor_operation()
+    @operation_for_version('beta')
     def destroySelfBreakReferences():
         """Delete the specified branch.
 
@@ -1113,6 +1131,7 @@
         queue=Reference(title=_('Branch Merge Queue'),
               schema=IBranchMergeQueue))
     @export_write_operation()
+    @operation_for_version('beta')
     def addToQueue(queue):
         """Add this branch to a specified queue.
 
@@ -1125,6 +1144,7 @@
     @operation_parameters(
         config=TextLine(title=_("A JSON string of config values.")))
     @export_write_operation()
+    @operation_for_version('beta')
     def setMergeQueueConfig(config):
         """Set the merge_queue_config property.
 
@@ -1156,6 +1176,7 @@
     @operation_parameters(
         private=Bool(title=_("Keep branch confidential")))
     @export_write_operation()
+    @operation_for_version('beta')
     def setPrivate(private, user):
         """Set the branch privacy for this branch."""
 
@@ -1239,6 +1260,7 @@
         unique_name=TextLine(title=_('Branch unique name'), required=True))
     @operation_returns_entry(IBranch)
     @export_read_operation()
+    @operation_for_version('beta')
     def getByUniqueName(unique_name):
         """Find a branch by its ~owner/product/name unique name.
 
@@ -1249,6 +1271,7 @@
         url=TextLine(title=_('Branch URL'), required=True))
     @operation_returns_entry(IBranch)
     @export_read_operation()
+    @operation_for_version('beta')
     def getByUrl(url):
         """Find a branch by URL.
 
@@ -1272,6 +1295,7 @@
             value_type=TextLine(),
             required=True))
     @export_read_operation()
+    @operation_for_version('beta')
     def getByUrls(urls):
         """Finds branches by URL.
 

=== modified file 'lib/lp/code/interfaces/branchlink.py'
--- lib/lp/code/interfaces/branchlink.py	2010-08-20 20:31:18 +0000
+++ lib/lp/code/interfaces/branchlink.py	2011-03-23 16:18:44 +0000
@@ -17,6 +17,7 @@
     export_operation_as,
     export_write_operation,
     exported,
+    operation_for_version,
     operation_parameters,
     operation_returns_entry,
     REQUEST_USER,
@@ -45,6 +46,7 @@
     @operation_parameters(
         branch=Reference(schema=IBranch))
     @export_write_operation()
+    @operation_for_version('beta')
     def linkBranch(branch, registrant):
         """Associate a branch with this bug.
 
@@ -56,6 +58,7 @@
     @operation_parameters(
         branch=Reference(schema=IBranch))
     @export_write_operation()
+    @operation_for_version('beta')
     def unlinkBranch(branch, user):
         """Unlink a branch from this bug.
 

=== modified file 'lib/lp/code/interfaces/hasbranches.py'
--- lib/lp/code/interfaces/hasbranches.py	2011-03-03 01:13:47 +0000
+++ lib/lp/code/interfaces/hasbranches.py	2011-03-23 16:18:44 +0000
@@ -18,6 +18,7 @@
     call_with,
     export_factory_operation,
     export_read_operation,
+    operation_for_version,
     operation_parameters,
     operation_returns_collection_of,
     REQUEST_USER,
@@ -59,6 +60,7 @@
     @call_with(visible_by_user=REQUEST_USER)
     @operation_returns_collection_of(Interface) # Really IBranch.
     @export_read_operation()
+    @operation_for_version('beta')
     def getBranches(status=None, visible_by_user=None,
                     modified_since=None, eager_load=False):
         """Returns all branches with the given lifecycle status.
@@ -90,6 +92,7 @@
     @call_with(visible_by_user=REQUEST_USER)
     @operation_returns_collection_of(Interface) # Really IBranchMergeProposal.
     @export_read_operation()
+    @operation_for_version('beta')
     def getMergeProposals(status=None, visible_by_user=None):
         """Returns all merge proposals of a given status.
 
@@ -116,6 +119,7 @@
     @call_with(visible_by_user=REQUEST_USER)
     @operation_returns_collection_of(Interface) # Really IBranchMergeProposal.
     @export_read_operation()
+    @operation_for_version('beta')
     def getRequestedReviews(status=None, visible_by_user=None):
         """Returns merge proposals where a person was asked to review.
 
@@ -150,6 +154,7 @@
         )
     @call_with(registrant=REQUEST_USER)
     @export_factory_operation(Interface, []) # Really ICodeImport.
+    @operation_for_version('beta')
     def newCodeImport(registrant=None, branch_name=None, rcs_type=None,
                       url=None, cvs_root=None, cvs_module=None, owner=None):
         """Create a new code import.

=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
=== modified file 'lib/lp/code/model/branch.py'
=== modified file 'lib/lp/code/model/branchcollection.py'
--- lib/lp/code/model/branchcollection.py	2011-03-22 02:14:15 +0000
+++ lib/lp/code/model/branchcollection.py	2011-03-23 16:18:44 +0000
@@ -203,15 +203,31 @@
                 cache = caches[link.branchID]
                 cache._associatedSuiteSourcePackages.append(
                     link.suite_sourcepackage)
-            load_related(Product, rows, ['productID'])
-            # So far have only needed the persons for their canonical_url - no
-            # need for validity etc in the /branches API call.
-            load_related(Person, rows,
-                ['ownerID', 'registrantID', 'reviewerID'])
-            for code_import in IStore(CodeImport).find(
-                CodeImport, CodeImport.branchID.is_in(branch_ids)):
-                cache = caches[code_import.branchID]
-                cache.code_import = code_import
+<<<<<<< TREE
+            load_related(Product, rows, ['productID'])
+            # So far have only needed the persons for their canonical_url - no
+            # need for validity etc in the /branches API call.
+            load_related(Person, rows,
+                ['ownerID', 'registrantID', 'reviewerID'])
+            for code_import in IStore(CodeImport).find(
+                CodeImport, CodeImport.branchID.is_in(branch_ids)):
+                cache = caches[code_import.branchID]
+                cache.code_import = code_import
+=======
+            load_related(Product, rows, ['productID'])
+            # So far have only needed the persons for their canonical_url - no
+            # need for validity etc in the /branches API call.
+            load_related(Person, rows,
+                ['ownerID', 'registrantID', 'reviewerID'])
+            # Cache all branches as having no code imports to prevent fruitless
+            # lookups on the ones we don't find.
+            for cache in caches.values():
+                cache.code_import = None
+            for code_import in IStore(CodeImport).find(
+                CodeImport, CodeImport.branchID.is_in(branch_ids)):
+                cache = caches[code_import.branchID]
+                cache.code_import = code_import
+>>>>>>> MERGE-SOURCE
         return DecoratedResultSet(resultset, pre_iter_hook=do_eager_load)
 
     def getMergeProposals(self, statuses=None, for_branches=None,

=== modified file 'lib/lp/code/model/branchmergeproposal.py'
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
=== modified file 'lib/lp/code/model/tests/test_branch.py'
=== modified file 'lib/lp/registry/interfaces/distribution.py'
--- lib/lp/registry/interfaces/distribution.py	2011-02-24 15:30:54 +0000
+++ lib/lp/registry/interfaces/distribution.py	2011-03-23 16:18:44 +0000
@@ -644,7 +644,7 @@
             source_name="apport",
             distro_series=series)[0].source_package_version
     """
-    export_as_webservice_entry()
+    export_as_webservice_entry(as_of="beta")
 
 
 class IBaseDistribution(IDistribution):

=== modified file 'lib/lp/registry/interfaces/location.py'
--- lib/lp/registry/interfaces/location.py	2010-10-27 20:56:33 +0000
+++ lib/lp/registry/interfaces/location.py	2011-03-23 16:18:44 +0000
@@ -24,6 +24,7 @@
     call_with,
     export_write_operation,
     exported,
+    operation_for_version,
     operation_parameters,
     REQUEST_USER,
     )
@@ -89,6 +90,7 @@
         longitude=copy_field(IHasLocation['longitude'], required=True),
         time_zone=copy_field(IHasLocation['time_zone'], required=True))
     @export_write_operation()
+    @operation_for_version('beta')
     def setLocation(latitude, longitude, time_zone, user):
         """Specify the location and time zone of a person."""
 

=== modified file 'lib/lp/registry/interfaces/milestone.py'
--- lib/lp/registry/interfaces/milestone.py	2011-02-24 15:29:18 +0000
+++ lib/lp/registry/interfaces/milestone.py	2011-03-23 16:18:44 +0000
@@ -24,6 +24,7 @@
     export_operation_as,
     export_read_operation,
     exported,
+    operation_for_version,
     operation_parameters,
     operation_returns_entry,
     rename_parameters_as,
@@ -181,6 +182,7 @@
     @export_factory_operation(
         IProductRelease,
         ['datereleased', 'changelog', 'release_notes'])
+    @operation_for_version('beta')
     def createProductRelease(owner, datereleased,
                              changelog=None, release_notes=None):
         """Create a new ProductRelease.
@@ -203,6 +205,7 @@
 
     @export_destructor_operation()
     @export_operation_as('delete')
+    @operation_for_version('beta')
     def destroySelf():
         """Delete this milestone.
 
@@ -279,6 +282,7 @@
         name=TextLine(title=_("Name"), required=True))
     @operation_returns_entry(IMilestone)
     @export_read_operation()
+    @operation_for_version('beta')
     def getMilestone(name):
         """Return a milestone with the given name for this object, or None."""
 

=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py	2011-03-22 04:40:12 +0000
+++ lib/lp/registry/interfaces/person.py	2011-03-23 16:18:44 +0000
@@ -58,6 +58,7 @@
     export_write_operation,
     exported,
     LAZR_WEBSERVICE_EXPORTED,
+    operation_for_version,
     operation_parameters,
     operation_returns_collection_of,
     operation_returns_entry,
@@ -1010,6 +1011,7 @@
         build_daily=Bool(),
         )
     @export_factory_operation(Interface, [])
+    @operation_for_version("beta")
     def createRecipe(name, description, recipe_text, distroseries,
                      registrant, daily_build_archive=None, build_daily=False):
         """Create a SourcePackageRecipe owned by this person.
@@ -1027,6 +1029,7 @@
     @operation_parameters(name=TextLine(required=True))
     @operation_returns_entry(Interface) # Really ISourcePackageRecipe.
     @export_read_operation()
+    @operation_for_version("beta")
     def getRecipe(name):
         """Return the person's recipe with the given name."""
 
@@ -1035,6 +1038,7 @@
 
     @call_with(requester=REQUEST_USER)
     @export_read_operation()
+    @operation_for_version("beta")
     def getArchiveSubscriptionURLs(requester):
         """Return private archive URLs that this person can see.
 
@@ -1047,6 +1051,7 @@
     @operation_parameters(
         archive=Reference(schema=Interface)) # Really IArchive
     @export_write_operation()
+    @operation_for_version("beta")
     def getArchiveSubscriptionURL(requester, archive):
         """Get a text line that is suitable to be used for a sources.list
         entry.
@@ -1076,6 +1081,7 @@
 
     @operation_returns_collection_of(Interface)
     @export_read_operation()
+    @operation_for_version("beta")
     def getBugSubscriberPackages():
         """Return the packages for which this person is a bug subscriber.
 
@@ -1369,6 +1375,7 @@
         name=TextLine(required=True, constraint=name_validator))
     @operation_returns_entry(Interface) # Really IArchive.
     @export_read_operation()
+    @operation_for_version("beta")
     def getPPAByName(name):
         """Return a PPA with the given name if it exists.
 
@@ -1383,6 +1390,7 @@
         displayname=TextLine(required=False),
         description=TextLine(required=False))
     @export_factory_operation(Interface, []) # Really IArchive.
+    @operation_for_version("beta")
     def createPPA(name=None, displayname=None, description=None):
         """Create a PPA.
 
@@ -1553,6 +1561,7 @@
     @operation_parameters(status=copy_field(ITeamMembership['status']))
     @operation_returns_collection_of(Interface) # Really IPerson
     @export_read_operation()
+    @operation_for_version("beta")
     def getMembersByStatus(status, orderby=None):
         """Return the people whose membership on this team match :status:.
 
@@ -1566,6 +1575,7 @@
     @call_with(requester=REQUEST_USER)
     @operation_parameters(team=copy_field(ITeamMembership['team']))
     @export_write_operation()
+    @operation_for_version("beta")
     def join(team, requester=None, may_subscribe_to_list=True):
         """Join the given team if its subscriptionpolicy is not RESTRICTED.
 
@@ -1592,6 +1602,7 @@
 
     @operation_parameters(team=copy_field(ITeamMembership['team']))
     @export_write_operation()
+    @operation_for_version("beta")
     def leave(team):
         """Leave the given team.
 
@@ -1605,6 +1616,7 @@
     @operation_parameters(
         visible=copy_field(ILocationRecord['visible'], required=True))
     @export_write_operation()
+    @operation_for_version("beta")
     def setLocationVisibility(visible):
         """Specify the visibility of a person's location and time zone."""
 
@@ -1628,6 +1640,7 @@
         status=copy_field(ITeamMembership['status']),
         comment=Text(required=False))
     @export_write_operation()
+    @operation_for_version("beta")
     def addMember(person, reviewer, status=TeamMembershipStatus.APPROVED,
                   comment=None, force_team_add=False,
                   may_subscribe_to_list=True):
@@ -1668,6 +1681,7 @@
         team=copy_field(ITeamMembership['team']),
         comment=Text())
     @export_write_operation()
+    @operation_for_version("beta")
     def acceptInvitationToBeMemberOf(team, comment):
         """Accept an invitation to become a member of the given team.
 
@@ -1680,6 +1694,7 @@
         team=copy_field(ITeamMembership['team']),
         comment=Text())
     @export_write_operation()
+    @operation_for_version("beta")
     def declineInvitationToBeMemberOf(team, comment):
         """Decline an invitation to become a member of the given team.
 
@@ -1693,6 +1708,7 @@
         team=copy_field(ITeamMembership['team']),
         comment=Text(required=False))
     @export_write_operation()
+    @operation_for_version("beta")
     def retractTeamMembership(team, user, comment=None):
         """Retract this team's membership in the given team.
 
@@ -2036,6 +2052,7 @@
     @export_factory_operation(
         ITeam, ['name', 'displayname', 'teamdescription',
                 'defaultmembershipperiod', 'defaultrenewalperiod'])
+    @operation_for_version("beta")
     def newTeam(teamowner, name, displayname, teamdescription=None,
                 subscriptionpolicy=TeamSubscriptionPolicy.MODERATED,
                 defaultmembershipperiod=None, defaultrenewalperiod=None):
@@ -2048,6 +2065,7 @@
         email=TextLine(required=True, constraint=email_validator))
     @operation_returns_entry(IPerson)
     @export_read_operation()
+    @operation_for_version("beta")
     def getByEmail(email):
         """Return the person with the given email address.
 
@@ -2081,6 +2099,7 @@
         text=TextLine(title=_("Search text"), default=u""))
     @operation_returns_collection_of(IPerson)
     @export_read_operation()
+    @operation_for_version("beta")
     def find(text=""):
         """Return all non-merged Persons and Teams whose name, displayname or
         email address match <text>.
@@ -2103,6 +2122,7 @@
         )
     @operation_returns_collection_of(IPerson)
     @export_read_operation()
+    @operation_for_version("beta")
     def findPerson(text="", exclude_inactive_accounts=True,
                    must_have_email=False,
                    created_after=None, created_before=None):
@@ -2134,6 +2154,7 @@
         text=TextLine(title=_("Search text"), default=u""))
     @operation_returns_collection_of(IPerson)
     @export_read_operation()
+    @operation_for_version("beta")
     def findTeam(text=""):
         """Return all Teams whose name, displayname or email address
         match <text>.

=== modified file 'lib/lp/registry/interfaces/productseries.py'
--- lib/lp/registry/interfaces/productseries.py	2011-03-08 15:28:40 +0000
+++ lib/lp/registry/interfaces/productseries.py	2011-03-23 16:18:44 +0000
@@ -239,10 +239,7 @@
         vocabulary=TranslationsBranchImportMode,
         required=True,
         description=_("Specify which files will be imported from the "
-                      "source code branch.")),
-        ('devel', {'exported': True}),
-        exported=False
-        )
+                      "source code branch.")), as_of="devel")
 
     potemplate_count = Int(
         title=_("The total number of POTemplates in this series."),

=== modified file 'lib/lp/registry/model/person.py'
=== modified file 'lib/lp/registry/model/product.py'
=== modified file 'lib/lp/services/database/bulk.py'
--- lib/lp/services/database/bulk.py	2011-03-22 00:18:47 +0000
+++ lib/lp/services/database/bulk.py	2011-03-23 16:18:44 +0000
@@ -5,8 +5,12 @@
 
 __metaclass__ = type
 __all__ = [
+<<<<<<< TREE
     'load',
     'load_related',
+=======
+    'load',
+>>>>>>> MERGE-SOURCE
     'reload',
     ]
 

=== modified file 'lib/lp/services/features/__init__.py'
--- lib/lp/services/features/__init__.py	2011-03-22 06:39:21 +0000
+++ lib/lp/services/features/__init__.py	2011-03-23 16:18:44 +0000
@@ -208,6 +208,7 @@
     if features is None:
         return None
     return features.getFlag(flag)
+<<<<<<< TREE
 
 
 def make_script_feature_controller(script_name):
@@ -223,3 +224,18 @@
 
     return FeatureController(
         ScopesForScript(script_name).lookup, StormFeatureRuleSource())
+=======
+
+
+def make_script_feature_controller(script_name):
+    """Create and install a `FeatureController` for the named script."""
+    # Avoid circular import.
+    from lp.services.features.flags import FeatureController
+    from lp.services.features.rulesource import StormFeatureRuleSource
+    from lp.services.features.scopes import ScopesForScript
+
+    install_feature_controller(
+        FeatureController(
+            ScopesForScript(script_name).lookup,
+            StormFeatureRuleSource()))
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/services/scripts/base.py'
--- lib/lp/services/scripts/base.py	2011-03-22 06:39:21 +0000
+++ lib/lp/services/scripts/base.py	2011-03-23 16:18:44 +0000
@@ -39,11 +39,20 @@
     setupInteractionByEmail,
     )
 from canonical.lp import initZopeless
-from lp.services.features import (
-    get_relevant_feature_controller,
-    install_feature_controller,
-    make_script_feature_controller,
-    )
+<<<<<<< TREE
+from lp.services.features import (
+    get_relevant_feature_controller,
+    install_feature_controller,
+    make_script_feature_controller,
+    )
+=======
+from lp.services.features import (
+    getFeatureFlag,
+    get_relevant_feature_controller,
+    install_feature_controller,
+    make_script_feature_controller,
+    )
+>>>>>>> MERGE-SOURCE
 from lp.services.scripts.interfaces.scriptactivity import IScriptActivitySet
 
 

=== modified file 'lib/lp/services/scripts/tests/test_feature_controller.py'
--- lib/lp/services/scripts/tests/test_feature_controller.py	2011-03-22 06:39:21 +0000
+++ lib/lp/services/scripts/tests/test_feature_controller.py	2011-03-23 16:18:44 +0000
@@ -1,3 +1,4 @@
+<<<<<<< TREE
 # Copyright 2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
@@ -63,3 +64,66 @@
         FakeScript(name="mongo").run()
         self.assertEqual(
             previous_controller, get_relevant_feature_controller())
+=======
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test feature controller in scripts."""
+
+__metaclass__ = type
+
+from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.services.features import (
+    get_relevant_feature_controller,
+    install_feature_controller,
+    )
+from lp.services.features.flags import NullFeatureController
+from lp.services.features.testing import FeatureFixture
+from lp.services.scripts.base import LaunchpadScript
+from lp.testing import TestCase
+from lp.testing.fakemethod import FakeMethod
+
+
+class FakeScript(LaunchpadScript):
+    """A dummy script that only records which feature controller is active."""
+
+    observed_feature_controller = object()
+
+    def __init__(self, name):
+        super(FakeScript, self).__init__(name=name, test_args=[])
+
+    def main(self):
+        self.observed_feature_controller = get_relevant_feature_controller()
+
+    # Shortcut some underpinnings of LaunchpadScript.run that we can't
+    # afford to have happen in tests.
+    _init_zca = FakeMethod()
+    _init_db = FakeMethod()
+    record_activity = FakeMethod()
+
+
+class TestScriptFeatureController(TestCase):
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestScriptFeatureController, self).setUp()
+        self.original_controller = get_relevant_feature_controller()
+
+    def tearDown(self):
+        install_feature_controller(self.original_controller)
+        super(TestScriptFeatureController, self).tearDown()
+
+    def test_script_installs_script_feature_controller(self):
+        script = FakeScript(name="bongo")
+        script_feature_controller = get_relevant_feature_controller()
+        self.assertNotEqual(
+            self.original_controller, script.observed_feature_controller)
+        self.assertNotEqual(None, script.observed_feature_controller)
+
+    def test_script_restores_feature_controller(self):
+        previous_controller = NullFeatureController()
+        install_feature_controller(previous_controller)
+        FakeScript(name="mongo").run()
+        self.assertEqual(
+            previous_controller, get_relevant_feature_controller())
+>>>>>>> MERGE-SOURCE

=== modified file 'lib/lp/soyuz/interfaces/buildrecords.py'
--- lib/lp/soyuz/interfaces/buildrecords.py	2010-08-20 20:31:18 +0000
+++ lib/lp/soyuz/interfaces/buildrecords.py	2011-03-23 16:18:44 +0000
@@ -19,6 +19,7 @@
 from lazr.restful.declarations import (
     call_with,
     export_read_operation,
+    operation_for_version,
     operation_parameters,
     operation_returns_collection_of,
     rename_parameters_as,
@@ -59,6 +60,7 @@
     # Really a IBuild see _schema_circular_imports.
     @operation_returns_collection_of(Interface)
     @export_read_operation()
+    @operation_for_version('beta')
     def getBuildRecords(build_state=None, name=None, pocket=None,
                         arch_tag=None, user=None, binary_only=True):
         """Return build records in the context it is implemented.

=== modified file 'lib/lp/testing/__init__.py'
=== modified file 'lib/lp/translations/interfaces/hastranslationimports.py'
--- lib/lp/translations/interfaces/hastranslationimports.py	2010-10-29 10:01:34 +0000
+++ lib/lp/translations/interfaces/hastranslationimports.py	2011-03-23 16:18:44 +0000
@@ -11,6 +11,7 @@
 from lazr.restful.declarations import (
     export_as_webservice_entry,
     export_read_operation,
+    operation_for_version,
     operation_parameters,
     operation_returns_collection_of,
     )
@@ -51,6 +52,7 @@
     # _schema_circular_imports.py.
     @operation_returns_collection_of(Interface)
     @export_read_operation()
+    @operation_for_version('beta')
     def getTranslationImportQueueEntries(import_status=None,
                                          file_extension=None):
         """Return entries in the translation import queue for this entity.

=== modified file 'lib/lp/translations/interfaces/hastranslationtemplates.py'
--- lib/lp/translations/interfaces/hastranslationtemplates.py	2010-11-15 16:25:05 +0000
+++ lib/lp/translations/interfaces/hastranslationtemplates.py	2011-03-23 16:18:44 +0000
@@ -10,6 +10,7 @@
 
 from lazr.restful.declarations import (
     export_read_operation,
+    operation_for_version,
     operation_returns_collection_of,
     )
 from zope.interface import Interface
@@ -84,6 +85,7 @@
 
     @export_read_operation()
     @operation_returns_collection_of(Interface)
+    @operation_for_version('beta')
     def getTranslationTemplates():
         """Return an iterator over all its translation templates.
 

=== modified file 'lib/lp/translations/interfaces/translationgroup.py'
--- lib/lp/translations/interfaces/translationgroup.py	2011-03-07 19:05:44 +0000
+++ lib/lp/translations/interfaces/translationgroup.py	2011-03-23 16:18:44 +0000
@@ -61,8 +61,7 @@
             descriptive, because it will be used in URLs. Examples:
             gnome-translation-project, ubuntu-translators."""),
             constraint=name_validator),
-            ('devel', {'exported': True}),
-            exported=False,
+                    as_of="devel"
             )
     title = exported(Title(
             title=_('Title'), required=True,
@@ -72,8 +71,7 @@
             add "translation group" to this title, or it will be shown
             double.
             """),),
-            ('devel', {'exported': True}),
-            exported=False,
+                     as_of="devel"
             )
     summary = Summary(
             title=_('Summary'), required=True,

=== modified file 'lib/lp/translations/interfaces/translationpolicy.py'
--- lib/lp/translations/interfaces/translationpolicy.py	2011-03-07 19:05:44 +0000
+++ lib/lp/translations/interfaces/translationpolicy.py	2011-03-23 16:18:44 +0000
@@ -46,20 +46,14 @@
             " role depends on the permissions policy selected below."),
         required=False,
         vocabulary='TranslationGroup',
-        schema=ITranslationGroup),
-        ('devel', {'exported': True}),
-        exported=False,
-        )
+        schema=ITranslationGroup), as_of="devel")
 
     translationpermission = exported(Choice(
         title=_("Translation permissions policy"),
         description=_("The policy this project or distribution uses to "
             " balance openness and control for their translations."),
         required=True,
-        vocabulary=TranslationPermission),
-        ('devel', {'exported': True}),
-        exported=False,
-        )
+        vocabulary=TranslationPermission), as_of="devel")
 
     def getTranslationGroups():
         """List all applicable translation groups.

=== modified file 'versions.cfg'
--- versions.cfg	2011-03-17 05:29:08 +0000
+++ versions.cfg	2011-03-23 16:18:44 +0000
@@ -34,7 +34,11 @@
 lazr.delegates = 1.2.0
 lazr.enum = 1.1.2
 lazr.lifecycle = 1.1
+<<<<<<< TREE
 lazr.restful = 0.17.5
+=======
+lazr.restful = 0.18.0
+>>>>>>> MERGE-SOURCE
 lazr.restfulclient = 0.11.2
 lazr.smtptest = 1.1
 lazr.testing = 0.1.1