← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~thumper/launchpad/ibranch-interface-smashing into lp:launchpad/devel

 

Tim Penhey has proposed merging lp:~thumper/launchpad/ibranch-interface-smashing into lp:launchpad/devel.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  #399547 Split IBranch into separate interfaces based on required permissions
  https://bugs.launchpad.net/bugs/399547


Break the IBranch interface into other interfaces based on the required permissions.

There should be no other changes necessary.

There are two slight oddities with the resulting code.

I changed the IPrivacy inheritance in IBranch to be something that Branch directly provides.  This is due to the fact that we were reimplmenting (or overriding) the private attribute defined by IPrivacy in the IBranch interface.

Secondly we are using an api mutator decorator on "private" which doesn't work when the private attibute isn't available in the same interface, so for now, I've left both private and setPrivate on IBranch.
-- 
https://code.launchpad.net/~thumper/launchpad/ibranch-interface-smashing/+merge/29969
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~thumper/launchpad/ibranch-interface-smashing into lp:launchpad/devel.
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml	2010-06-16 18:18:32 +0000
+++ lib/lp/code/configure.zcml	2010-07-15 10:13:45 +0000
@@ -435,129 +435,23 @@
   <class class="lp.code.model.branch.Branch">
     <require
         permission="launchpad.View"
-        attributes="id
-                    branch_type
-                    name
-                    url
-                    composePublicURL
-                    whiteboard
-                    target
-                    mirror_status_message
-                    private
-                    registrant
-                    owner
-                    description
-                    author
-                    reviewer
-                    code_reviewer
-                    isPersonTrustedReviewer
-                    product
-                    unique_name
-                    displayname
-                    sort_key
-                    lifecycle_status
-                    last_mirrored
-                    last_mirrored_id
-                    last_mirror_attempt
-                    mirror_failures
-                    pull_disabled
-                    next_mirror_time
-                    last_scanned
-                    last_scanned_id
-                    revision_count
-                    bug_branches
-                    linked_bugs
-                    getLinkedBugsAndTasks
-                    linkBug
-                    unlinkBug
-                    spec_links
-                    linkSpecification
-                    unlinkSpecification
-                    revision_history
-                    subscriptions
-                    subscribers
-                    date_created
-                    date_last_modified
-                    latest_revisions
-                    landing_targets
-                    landing_candidates
-                    dependent_branches
-                    _createMergeProposal
-                    addLandingTarget
-                    scheduleDiffUpdates
-                    getMergeQueue
-                    getRevisionsSince
-                    code_is_browseable
-                    browse_source_url
-                    code_import
-                    bzr_identity
-                    canBeDeleted
-                    deletionRequirements
-                    associatedProductSeries
-                    getProductSeriesPushingTranslations
-                    associatedSuiteSourcePackages
-                    branchIdentities
-                    branchLinks
-                    subscribe
-                    getSubscription
-                    hasSubscription
-                    unsubscribe
-                    getSubscriptionsByLevel
-                    getBranchRevision
-                    getMainlineBranchRevisions
-                    getMergeProposals
-                    getStackedBranches
-                    createBranchRevision
-                    getTipRevision
-                    updateScannedDetails
-                    getNotificationRecipients
-                    getScannerData
-                    getPullURL
-                    getInternalBzrUrl
-                    getBzrBranch
-                    requestMirror
-                    startMirroring
-                    mirrorFailed
-                    branch_format
-                    repository_format
-                    control_format
-                    stacked_on
-                    createBranchRevisionFromIDs
-                    distroseries
-                    sourcepackagename
-                    addToLaunchBag
-                    distribution
-                    sourcepackage
-                    codebrowse_url
-                    merge_queue
-                    namespace
-                    pending_writes
-                    commitsForDays
-                    needs_upgrading
-                    upgrade_pending
-                    getUpgradeFormat
-                    isBranchMergeable
-                    visibleByUser
-                    getRecipes
-                    "/>
+        interface="canonical.launchpad.interfaces.launchpad.IPrivacy
+                   lp.code.interfaces.branch.IBranchAnyone
+                   lp.code.interfaces.branch.IBranchEditableAttributes
+                   lp.code.interfaces.branch.IBranchPublic
+                   lp.code.interfaces.branch.IBranchView
+                   "/>
     <require
         permission="launchpad.Edit"
-        attributes="destroySelf destroySelfBreakReferences setPrivate
-                    setOwner setTarget requestUpgrade branchChanged"
-        set_attributes="name url mirror_status_message
-                        description lifecycle_status
-                        last_mirrored last_mirrored_id last_mirror_attempt
-                        mirror_failures pull_disabled next_mirror_time
-                        last_scanned last_scanned_id revision_count branch_type
-                        reviewer branch_format repository_format
-                        control_format stacked_on merge_queue
-                        merge_control_status"/>
+        interface="lp.code.interfaces.branch.IBranchEdit"
+        set_schema="lp.code.interfaces.branch.IBranchEditableAttributes"
+        attributes="setPrivate"/>
     <require
         permission="launchpad.AnyPerson"
-        set_attributes="whiteboard"/>
+        set_schema="lp.code.interfaces.branch.IBranchAnyone"/>
     <require
         permission="zope.Public"
-        set_attributes="date_last_modified"/>
+        set_schema="lp.code.interfaces.branch.IBranchPublic"/>
   </class>
   <adapter
       for="lp.code.interfaces.branch.IBranch"

=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py	2010-07-09 10:22:32 +0000
+++ lib/lp/code/interfaces/branch.py	2010-07-15 10:13:45 +0000
@@ -57,12 +57,17 @@
 from canonical.launchpad import _
 from canonical.launchpad.fields import (
     ParticipatingPersonChoice, PublicPersonChoice, URIField, Whiteboard)
+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
 from canonical.launchpad.validators import LaunchpadValidationError
+from canonical.launchpad.webapp.interfaces import (
+    ITableBatchNavigator, NameLookupFailed)
+from canonical.launchpad.webapp.menu import structured
 from lp.code.bzr import BranchFormat, ControlFormat, RepositoryFormat
 from lp.code.enums import (
     BranchLifecycleStatus,
     BranchMergeControlStatus,
-    BranchSubscriptionNotificationLevel, BranchSubscriptionDiffSize,
+    BranchSubscriptionDiffSize,
+    BranchSubscriptionNotificationLevel,
     CodeReviewNotificationLevel,
     UICreatableBranchType,
     )
@@ -71,14 +76,9 @@
 from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
 from lp.code.interfaces.hasbranches import IHasMergeProposals
 from lp.code.interfaces.hasrecipes import IHasRecipes
-from canonical.launchpad.interfaces.launchpad import (
-    ILaunchpadCelebrities, IPrivacy)
 from lp.registry.interfaces.role import IHasOwner
 from lp.registry.interfaces.person import IPerson
 from lp.registry.interfaces.pocket import PackagePublishingPocket
-from canonical.launchpad.webapp.interfaces import (
-    ITableBatchNavigator, NameLookupFailed)
-from canonical.launchpad.webapp.menu import structured
 
 
 DEFAULT_BRANCH_STATUS_IN_LISTING = (
@@ -317,50 +317,30 @@
     """A marker interface to indicate the need to show the branch menu."""
 
 
-class IBranch(IHasOwner, IPrivacy, IHasBranchTarget, IHasMergeProposals,
-              IHasRecipes):
-    """A Bazaar branch."""
-
-    # Mark branches as exported entries for the Launchpad API.
-    export_as_webservice_entry(plural_name='branches')
+class IBranchPublic(Interface):
+    """Public attributes for a branch."""
+
+    date_last_modified = exported(
+        Datetime(
+            title=_('Date Last Modified'),
+            required=True,
+            readonly=False))
+
+class IBranchAnyone(Interface):
+    """Attributes of IBranch that can be changed by launchpad.AnyPerson."""
+
+    whiteboard = exported(
+        Whiteboard(
+            title=_('Whiteboard'), required=False,
+            description=_('Notes on the current status of the branch.')))
+
+
+class IBranchView(IHasOwner, IHasBranchTarget, IHasMergeProposals,
+                  IHasRecipes):
+    """IBranch attributes that require launchpad.View permission."""
 
     id = Int(title=_('ID'), readonly=True, required=True)
 
-    # XXX: TimPenhey 2007-08-31
-    # The vocabulary set for branch_type is only used for the creation
-    # of branches through the automatically generated forms, and doesn't
-    # actually represent the complete range of real values that branch_type
-    # may actually hold.  Import branches are not created in the same
-    # way as Hosted, Mirrored or Remote branches.
-    # There are two option:
-    #   1) define a separate schema to use in the UI (sledgehammer solution)
-    #   2) work out some way to specify a restricted vocabulary in the view
-    # Personally I'd like a LAZR way to do number 2.
-    branch_type = exported(
-        Choice(
-            title=_("Branch Type"), required=True, readonly=True,
-            vocabulary=UICreatableBranchType))
-
-    name = exported(
-        TextLine(
-            title=_('Name'), required=True, constraint=branch_name_validator,
-            description=_(
-                "Keep very short, unique, and descriptive, because it will "
-                "be used in URLs.  "
-                "Examples: main, devel, release-1.0, gnome-vfs.")))
-
-    url = exported(
-        BranchURIField(
-            title=_('Branch URL'), required=False,
-            allowed_schemes=['http', 'https', 'ftp', 'sftp', 'bzr+ssh'],
-            allow_userinfo=False,
-            allow_query=False,
-            allow_fragment=False,
-            trailing_slash=False,
-            description=_(
-                "This is the external location where the Bazaar "
-                "branch is hosted.")))
-
     @operation_parameters(
         scheme=TextLine(title=_("URL scheme"), default=u'http'))
     @export_read_operation()
@@ -372,57 +352,6 @@
             accepted).
         """
 
-    description = exported(
-        Text(
-            title=_('Description'), required=False,
-            description=_(
-                'A short description of the changes in this branch.')))
-
-    branch_format = exported(
-        Choice(
-            title=_("Branch Format"),
-            required=False, readonly=True,
-            vocabulary=BranchFormat))
-
-    repository_format = exported(
-        Choice(
-            title=_("Repository Format"),
-            required=False, readonly=True,
-            vocabulary=RepositoryFormat))
-
-    control_format = exported(
-        Choice(
-            title=_("Control Directory"),
-            required=False, readonly=True,
-            vocabulary=ControlFormat))
-
-    whiteboard = exported(
-        Whiteboard(
-            title=_('Whiteboard'), required=False,
-            description=_('Notes on the current status of the branch.')))
-
-    mirror_status_message = exported(
-        Text(
-            title=_('The last message we got when mirroring this branch.'),
-            required=False, readonly=True))
-
-    # This is redefined from IPrivacy.private because the attribute is
-    # read-only. The value is guarded by setPrivate().
-    private = exported(
-        Bool(
-            title=_("Keep branch confidential"), required=False,
-            readonly=True, default=False,
-            description=_(
-                "Make this branch visible only to its subscribers.")))
-
-    @mutator_for(private)
-    @call_with(user=REQUEST_USER)
-    @operation_parameters(
-        private=Bool(title=_("Keep branch confidential")))
-    @export_write_operation()
-    def setPrivate(private, user):
-        """Set the branch privacy for this branch."""
-
     # People attributes
     registrant = exported(
         PublicPersonChoice(
@@ -438,44 +367,6 @@
             description=_("Either yourself or a team you are a member of. "
                           "This controls who can modify the branch.")))
 
-    @call_with(user=REQUEST_USER)
-    @operation_parameters(
-        new_owner=Reference(
-            title=_("The new owner of the branch."),
-            schema=IPerson))
-    @export_write_operation()
-    def setOwner(new_owner, user):
-        """Set the owner of the branch to be `new_owner`."""
-
-    @call_with(user=REQUEST_USER)
-    @operation_parameters(
-        project=Reference(
-            title=_("The project the branch belongs to."),
-            schema=Interface, required=False), # Really IProduct
-        source_package=Reference(
-            title=_("The source package the branch belongs to."),
-            schema=Interface, required=False)) # Really ISourcePackage
-    @export_write_operation()
-    def setTarget(user, project=None, source_package=None):
-        """Set the target of the branch to be `project` or `source_package`.
-
-        Only one of `project` or `source_package` can be set, and if neither
-        is set, the branch gets moved into the junk namespace of the branch
-        owner.
-
-        :raise: `BranchTargetError` if both project and source_package are set,
-          or if either the project or source_package fail to be adapted to an
-          IBranchTarget.
-        """
-
-    reviewer = exported(
-        PublicPersonChoice(
-            title=_('Review Team'),
-            required=False,
-            vocabulary='ValidPersonOrTeam',
-            description=_("The reviewer of a branch is the person or team "
-                          "that is responsible for reviewing proposals and "
-                          "merging into this branch.")))
 
     # Distroseries and sourcepackagename are exported together as
     # the sourcepackage.
@@ -505,22 +396,6 @@
                     "None if not a package branch."),
             schema=Interface, required=False, readonly=True))
 
-    code_reviewer = Attribute(
-        "The reviewer if set, otherwise the owner of the branch.")
-
-    @operation_parameters(
-        reviewer=Reference(
-            title=_("A person for which the reviewer status is in question."),
-            schema=IPerson))
-    @export_read_operation()
-    def isPersonTrustedReviewer(reviewer):
-        """Return true if the `reviewer` is a trusted reviewer.
-
-        The reviewer is trusted if they are either own the branch, or are in
-        the team that owns the branch, or they are in the review team for the
-        branch.
-        """
-
     namespace = Attribute(
         "The namespace of this branch, as an `IBranchNamespace`.")
 
@@ -549,34 +424,42 @@
                 "The branch unique_name.")),
         exported_as='display_name')
 
-    # Stats and status attributes
-    lifecycle_status = exported(
-        Choice(
-            title=_('Status'), vocabulary=BranchLifecycleStatus,
-            default=BranchLifecycleStatus.DEVELOPMENT))
-
-    # Mirroring attributes. For more information about how these all relate to
-    # each other, look at
-    # 'lib/canonical/launchpad/doc/puller-state-table.ods'.
+    code_reviewer = Attribute(
+        "The reviewer if set, otherwise the owner of the branch.")
+
+    @operation_parameters(
+        reviewer=Reference(
+            title=_("A person for which the reviewer status is in question."),
+            schema=IPerson))
+    @export_read_operation()
+    def isPersonTrustedReviewer(reviewer):
+        """Return true if the `reviewer` is a trusted reviewer.
+
+        The reviewer is trusted if they are either own the branch, or are in
+        the team that owns the branch, or they are in the review team for the
+        branch.
+        """
+
     last_mirrored = exported(
         Datetime(
             title=_("Last time this branch was successfully mirrored."),
             required=False, readonly=True))
     last_mirrored_id = Text(
-        title=_("Last mirrored revision ID"), required=False,
+        title=_("Last mirrored revision ID"), required=False, readonly=True,
         description=_("The head revision ID of the branch when last "
                       "successfully mirrored."))
     last_mirror_attempt = exported(
         Datetime(
             title=_("Last time a mirror of this branch was attempted."),
             required=False, readonly=True))
+
     mirror_failures = Attribute(
         "Number of failed mirror attempts since the last successful mirror.")
+
     next_mirror_time = Datetime(
         title=_("If this value is more recent than the last mirror attempt, "
                 "then the branch will be mirrored on the next mirror run."),
         required=False)
-
     # Scanning attributes
     last_scanned = exported(
         Datetime(
@@ -596,6 +479,9 @@
 
     stacked_on = Attribute('Stacked-on branch')
 
+    merge_queue = Attribute(
+        "The queue that contains the QUEUED proposals for this branch.")
+
     # Bug attributes
     bug_branches = CollectionField(
             title=_("The bug-branch link objects that link this branch "
@@ -663,9 +549,6 @@
         :param user: IPerson unlinking the spec.
         """
 
-    pending_writes = Attribute(
-        "Whether there is new Bazaar data for this branch.")
-
     # Joins
     revision_history = Attribute(
         """The sequence of BranchRevision for the mainline of that branch.
@@ -693,30 +576,8 @@
             required=True,
             readonly=True))
 
-    date_last_modified = exported(
-        Datetime(
-            title=_('Date Last Modified'),
-            required=True,
-            readonly=False))
-
-    @export_destructor_operation()
-    def destroySelfBreakReferences():
-        """Delete the specified branch.
-
-        BranchRevisions associated with this branch will also be deleted as 
-        well as any items with mandatory references.
-        """
-
-    def destroySelf(break_references=False):
-        """Delete the specified branch.
-
-        BranchRevisions associated with this branch will also be deleted.
-
-        :param break_references: If supplied, break any references to this
-            branch by deleting items with mandatory references and
-            NULLing other references.
-        :raise: CannotDeleteBranch if the branch cannot be deleted.
-        """
+    pending_writes = Attribute(
+        "Whether there is new Bazaar data for this branch.")
 
     def latest_revisions(quantity=10):
         """A specific number of the latest revisions in that branch."""
@@ -815,14 +676,6 @@
     def getStackedBranches():
         """The branches that are stacked on this one."""
 
-    merge_queue = Attribute(
-        "The queue that contains the QUEUED proposals for this branch.")
-
-    merge_control_status = Choice(
-        title=_('Merge Control Status'), required=True,
-        vocabulary=BranchMergeControlStatus,
-        default=BranchMergeControlStatus.NO_QUEUE)
-
     def getMergeQueue():
         """The proposals that are QUEUED to land on this branch."""
 
@@ -1111,23 +964,6 @@
     def startMirroring():
         """Signal that this branch is being mirrored."""
 
-    def branchChanged(stacked_on_url, last_revision_id, control_format,
-                      branch_format, repository_format):
-        """Record that a branch has been changed.
-
-        This method records the stacked on branch tip revision id and format
-        or the branch and creates a scan job if the tip revision id has
-        changed.
-
-        :param stacked_on_url: The unique name of the branch this branch is
-            stacked on, or '' if this branch is not stacked.
-        :param last_revision_id: The tip revision ID of the branch.
-        :param control_format: The entry from ControlFormat for the branch.
-        :param branch_format: The entry from BranchFormat for the branch.
-        :param repository_format: The entry from RepositoryFormat for the
-            branch.
-        """
-
     def mirrorFailed(reason):
         """Signal that a mirror attempt failed.
 
@@ -1148,11 +984,196 @@
     upgrade_pending = Attribute(
         "Whether a branch has had an upgrade requested.")
 
+    def visibleByUser(user):
+        """Can the specified user see this branch?"""
+
+
+class IBranchEditableAttributes(Interface):
+    """IBranch attributes that can be edited.
+
+    These attributes need launchpad.View to see, and launchpad.Edit to change.
+    """
+
+    name = exported(
+        TextLine(
+            title=_('Name'), required=True, constraint=branch_name_validator,
+            description=_(
+                "Keep very short, unique, and descriptive, because it will "
+                "be used in URLs.  "
+                "Examples: main, devel, release-1.0, gnome-vfs.")))
+
+    reviewer = exported(
+        PublicPersonChoice(
+            title=_('Review Team'),
+            required=False,
+            vocabulary='ValidPersonOrTeam',
+            description=_("The reviewer of a branch is the person or team "
+                          "that is responsible for reviewing proposals and "
+                          "merging into this branch.")))
+
+    url = exported(
+        BranchURIField(
+            title=_('Branch URL'), required=False,
+            allowed_schemes=['http', 'https', 'ftp', 'sftp', 'bzr+ssh'],
+            allow_userinfo=False,
+            allow_query=False,
+            allow_fragment=False,
+            trailing_slash=False,
+            description=_(
+                "This is the external location where the Bazaar "
+                "branch is hosted.")))
+
+    mirror_status_message = exported(
+        Text(
+            title=_('The last message we got when mirroring this branch.'),
+            required=False, readonly=True))
+
+    # XXX: TimPenhey 2007-08-31
+    # The vocabulary set for branch_type is only used for the creation
+    # of branches through the automatically generated forms, and doesn't
+    # actually represent the complete range of real values that branch_type
+    # may actually hold.  Import branches are not created in the same
+    # way as Hosted, Mirrored or Remote branches.
+    # There are two option:
+    #   1) define a separate schema to use in the UI (sledgehammer solution)
+    #   2) work out some way to specify a restricted vocabulary in the view
+    # Personally I'd like a LAZR way to do number 2.
+    branch_type = exported(
+        Choice(
+            title=_("Branch Type"), required=True, readonly=True,
+            vocabulary=UICreatableBranchType))
+
+    description = exported(
+        Text(
+            title=_('Description'), required=False,
+            description=_(
+                'A short description of the changes in this branch.')))
+
+    lifecycle_status = exported(
+        Choice(
+            title=_('Status'), vocabulary=BranchLifecycleStatus,
+            default=BranchLifecycleStatus.DEVELOPMENT))
+
+    branch_format = exported(
+        Choice(
+            title=_("Branch Format"),
+            required=False, readonly=True,
+            vocabulary=BranchFormat))
+
+    repository_format = exported(
+        Choice(
+            title=_("Repository Format"),
+            required=False, readonly=True,
+            vocabulary=RepositoryFormat))
+
+    control_format = exported(
+        Choice(
+            title=_("Control Directory"),
+            required=False, readonly=True,
+            vocabulary=ControlFormat))
+
+    merge_control_status = Choice(
+        title=_('Merge Control Status'), required=True,
+        vocabulary=BranchMergeControlStatus,
+        default=BranchMergeControlStatus.NO_QUEUE)
+
+
+class IBranchEdit(Interface):
+    """IBranch attributes that require launchpad.Edit permission."""
+
+    @call_with(user=REQUEST_USER)
+    @operation_parameters(
+        new_owner=Reference(
+            title=_("The new owner of the branch."),
+            schema=IPerson))
+    @export_write_operation()
+    def setOwner(new_owner, user):
+        """Set the owner of the branch to be `new_owner`."""
+
+    @call_with(user=REQUEST_USER)
+    @operation_parameters(
+        project=Reference(
+            title=_("The project the branch belongs to."),
+            schema=Interface, required=False), # Really IProduct
+        source_package=Reference(
+            title=_("The source package the branch belongs to."),
+            schema=Interface, required=False)) # Really ISourcePackage
+    @export_write_operation()
+    def setTarget(user, project=None, source_package=None):
+        """Set the target of the branch to be `project` or `source_package`.
+
+        Only one of `project` or `source_package` can be set, and if neither
+        is set, the branch gets moved into the junk namespace of the branch
+        owner.
+
+        :raise: `BranchTargetError` if both project and source_package are set,
+          or if either the project or source_package fail to be adapted to an
+          IBranchTarget.
+        """
+
     def requestUpgrade():
         """Create an IBranchUpgradeJob to upgrade this branch."""
 
-    def visibleByUser(user):
-        """Can the specified user see this branch?"""
+    def branchChanged(stacked_on_url, last_revision_id, control_format,
+                      branch_format, repository_format):
+        """Record that a branch has been changed.
+
+        This method records the stacked on branch tip revision id and format
+        or the branch and creates a scan job if the tip revision id has
+        changed.
+
+        :param stacked_on_url: The unique name of the branch this branch is
+            stacked on, or '' if this branch is not stacked.
+        :param last_revision_id: The tip revision ID of the branch.
+        :param control_format: The entry from ControlFormat for the branch.
+        :param branch_format: The entry from BranchFormat for the branch.
+        :param repository_format: The entry from RepositoryFormat for the
+            branch.
+        """
+
+    @export_destructor_operation()
+    def destroySelfBreakReferences():
+        """Delete the specified branch.
+
+        BranchRevisions associated with this branch will also be deleted as
+        well as any items with mandatory references.
+        """
+
+    def destroySelf(break_references=False):
+        """Delete the specified branch.
+
+        BranchRevisions associated with this branch will also be deleted.
+
+        :param break_references: If supplied, break any references to this
+            branch by deleting items with mandatory references and
+            NULLing other references.
+        :raise: CannotDeleteBranch if the branch cannot be deleted.
+        """
+
+
+class IBranch(IBranchPublic, IBranchView, IBranchEdit,
+              IBranchEditableAttributes, IBranchAnyone):
+    """A Bazaar branch."""
+
+    # Mark branches as exported entries for the Launchpad API.
+    export_as_webservice_entry(plural_name='branches')
+
+    # This is redefined from IPrivacy.private because the attribute is
+    # read-only. The value is guarded by setPrivate().
+    private = exported(
+        Bool(
+            title=_("Keep branch confidential"), required=False,
+            readonly=True, default=False,
+            description=_(
+                "Make this branch visible only to its subscribers.")))
+
+    @mutator_for(private)
+    @call_with(user=REQUEST_USER)
+    @operation_parameters(
+        private=Bool(title=_("Keep branch confidential")))
+    @export_write_operation()
+    def setPrivate(private, user):
+        """Set the branch privacy for this branch."""
 
 
 class IBranchSet(Interface):

=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py	2010-07-09 10:22:32 +0000
+++ lib/lp/code/model/branch.py	2010-07-15 10:13:45 +0000
@@ -40,7 +40,8 @@
 
 from canonical.launchpad import _
 from lp.services.job.model.job import Job
-from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
+from canonical.launchpad.interfaces.launchpad import (
+    ILaunchpadCelebrities, IPrivacy)
 from canonical.launchpad.webapp import urlappend
 from canonical.launchpad.webapp.interfaces import (
     IStoreSelector, MAIN_STORE, SLAVE_FLAVOR)
@@ -87,7 +88,7 @@
 class Branch(SQLBase, BzrIdentityMixin):
     """A sequence of ordered revisions in Bazaar."""
 
-    implements(IBranch, IBranchNavigationMenu)
+    implements(IBranch, IBranchNavigationMenu, IPrivacy)
     _table = 'Branch'
 
     branch_type = EnumCol(enum=BranchType, notNull=True)