~launchpad-pqm/launchpad/devel

10312.5.5 by Jeroen Vermeulen
IBranch.composePublicURL.
1
# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
8687.15.17 by Karl Fogel
Add the copyright header block to the rest of the files under lib/lp/.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
8511.2.5 by Aaron Bentley
Fix lint errors
4
# pylint: disable-msg=E0611,W0212,W0141,F0401
1670 by Canonical.com Patch Queue Manager
Big lot of database clean-up r=stub except for resolution of conflicts.
5
6
__metaclass__ = type
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
7
__all__ = [
8
    'Branch',
9
    'BranchSet',
10
    ]
1102.1.52 by David Allouche
remove archbranch.py, fix branch goo until the product page works
11
6233.4.1 by Paul Hummer
Removed the hide_dormant checkbox
12
from datetime import datetime
11822.4.3 by Robert Collins
Really drop last uses of prejoin.
13
import operator
3215.1.4 by Andrew Bennetts
Use properly validated URLs in the configuration, as suggested by Bjorn.
14
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
15
from bzrlib import urlutils
7236.4.1 by Tim Penhey
Move the functionality into the branch class for update scanned details, and awaken inactive branches on new revisions.
16
from bzrlib.revision import NULL_REVISION
4652.1.7 by Jonathan Lange
Use mirror_request_time for all branches, making sure that mirrorComplete
17
import pytz
12499.1.6 by Leonard Richardson
Fix imports.
18
import simplejson
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
19
from sqlobject import (
20
    BoolCol,
21
    ForeignKey,
22
    IntCol,
23
    SQLMultipleJoin,
24
    SQLRelatedJoin,
25
    StringCol,
26
    )
27
from storm.expr import (
28
    And,
29
    Count,
30
    Desc,
31
    NamedFunc,
32
    Not,
33
    Or,
34
    Select,
35
    )
7675.887.20 by Paul Hummer
Added IBranch.addToQueue with accompanying tests
36
from storm.locals import (
37
    AutoReload,
38
    Int,
39
    Reference,
40
    )
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
41
from storm.store import Store
5543.8.4 by Tim Penhey
Hooks in progress.
42
from zope.component import getUtility
43
from zope.event import notify
7735.3.1 by Aaron Bentley
Split branchjob out of branch
44
from zope.interface import implements
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
45
from zope.security.proxy import (
46
    ProxyFactory,
47
    removeSecurityProxy,
48
    )
10312.5.5 by Jeroen Vermeulen
IBranch.composePublicURL.
49
3215.1.3 by Andrew Bennetts
Generate different pull_urls for imported branches, and add some configuration options for pull url prefixes.
50
from canonical.config import config
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
51
from canonical.database.constants import (
52
    DEFAULT,
53
    UTC_NOW,
54
    )
1102.1.52 by David Allouche
remove archbranch.py, fix branch goo until the product page works
55
from canonical.database.datetimecol import UtcDateTimeCol
3691.373.5 by Christian Reis
Move DBSchema and Item into webapp.enum, and put EnumCol into canonical.database.enumcol
56
from canonical.database.enumcol import EnumCol
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
57
from canonical.database.sqlbase import (
58
    SQLBase,
59
    sqlvalues,
60
    )
5616.2.5 by Aaron Bentley
Corrections from make lint
61
from canonical.launchpad import _
11822.4.3 by Robert Collins
Really drop last uses of prejoin.
62
from canonical.launchpad.components.decoratedresultset import (
63
    DecoratedResultSet,
64
    )
12457.2.6 by Robert Collins
Review feedback: use a shortlist, don't spread the any pollution of default symbols. Docstring fixup and unbreak the legacy linked_bugs.
65
from canonical.launchpad.helpers import shortlist
11134.6.2 by Tim Penhey
More interface mangling.
66
from canonical.launchpad.interfaces.launchpad import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
67
    ILaunchpadCelebrities,
68
    IPrivacy,
69
    )
7675.814.4 by Tim Penhey
Add implementation and remove lint.
70
from canonical.launchpad.interfaces.lpstorm import IMasterStore
8225.3.2 by Tim Penhey
Sort the imports.
71
from canonical.launchpad.webapp import urlappend
7675.708.15 by Tim Penhey
Add the check to make sure that the user unsubscribing actually is allowed to.
72
from lp.app.errors import UserCannotUnsubscribePerson
12457.2.3 by Robert Collins
Lock down scaling of branch:+index for product bugtasks - still to do productseries and distroseries.
73
from lp.bugs.interfaces.bugtask import (
12499.1.24 by Leonard Richardson
Added missing import.
74
    BugTaskSearchParams,
12457.2.3 by Robert Collins
Lock down scaling of branch:+index for product bugtasks - still to do productseries and distroseries.
75
    IBugTaskSet,
76
    )
12588.3.21 by Tim Penhey
Remove branch specific bug task filtering, and add the possibility of titles to the generic link formatter.
77
from lp.bugs.interfaces.bugtaskfilter import filter_bugtasks_by_context
11134.3.2 by Jeroen Vermeulen
Clean up BuildQueues for BranchJobs.
78
from lp.buildmaster.model.buildqueue import BuildQueue
8555.2.1 by Tim Penhey
Create lp/code level modules for enums, separating out those that rely on bzrlib.
79
from lp.code.bzr import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
80
    BranchFormat,
81
    ControlFormat,
82
    CURRENT_BRANCH_FORMATS,
83
    CURRENT_REPOSITORY_FORMATS,
84
    RepositoryFormat,
85
    )
8555.2.2 by Tim Penhey
Move enum -> enums.
86
from lp.code.enums import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
87
    BranchLifecycleStatus,
88
    BranchMergeProposalStatus,
89
    BranchType,
90
    )
7675.429.3 by Tim Penhey
Move merge proposal errors to lp.code.errors.
91
from lp.code.errors import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
92
    BranchCannotBePrivate,
93
    BranchCannotBePublic,
94
    BranchMergeProposalExists,
95
    BranchTargetError,
96
    BranchTypeError,
97
    CannotDeleteBranch,
98
    InvalidBranchMergeProposal,
7675.887.21 by Paul Hummer
Added IBranch.setMergeQueueConfig with accompanying tests
99
    InvalidMergeQueueConfig,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
100
    )
11733.1.4 by Tim Penhey
Add an extra event for the needs review, but keep the same functionality for now.
101
from lp.code.event.branchmergeproposal import (
102
    BranchMergeProposalNeedsReviewEvent,
103
    NewBranchMergeProposalEvent,
104
    )
8225.3.2 by Tim Penhey
Sort the imports.
105
from lp.code.interfaces.branch import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
106
    BzrIdentityMixin,
107
    DEFAULT_BRANCH_STATUS_IN_LISTING,
108
    IBranch,
109
    IBranchNavigationMenu,
110
    IBranchSet,
111
    user_has_special_branch_access,
112
    )
8138.1.2 by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing.
113
from lp.code.interfaces.branchcollection import IAllBranches
8777.4.6 by Jonathan Lange
Merge the new branches interface with branch set.
114
from lp.code.interfaces.branchlookup import IBranchLookup
8138.1.2 by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing.
115
from lp.code.interfaces.branchmergeproposal import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
116
    BRANCH_MERGE_PROPOSAL_FINAL_STATES,
117
    )
8137.17.24 by Barry Warsaw
thread merge
118
from lp.code.interfaces.branchnamespace import IBranchNamespacePolicy
8138.1.2 by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing.
119
from lp.code.interfaces.branchpuller import IBranchPuller
120
from lp.code.interfaces.branchtarget import IBranchTarget
12707.4.8 by Tim Penhey
Support the branch-id alias in the setting of the stacked on branch in the model code.
121
from lp.code.interfaces.codehosting import (
122
    BRANCH_ID_ALIAS_PREFIX,
123
    compose_public_url,
124
    )
8211.4.12 by Jonathan Lange
Allow branches linked to source packages to be deleted.
125
from lp.code.interfaces.seriessourcepackagebranch import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
126
    IFindOfficialBranchLinks,
127
    )
128
from lp.code.mail.branch import send_branch_modified_notifications
129
from lp.code.model.branchmergeproposal import (
130
    BranchMergeProposal,
131
    BranchMergeProposalGetter,
132
    )
133
from lp.code.model.branchrevision import BranchRevision
134
from lp.code.model.branchsubscription import BranchSubscription
135
from lp.code.model.revision import (
136
    Revision,
137
    RevisionAuthor,
138
    )
139
from lp.code.model.seriessourcepackagebranch import SeriesSourcePackageBranch
9590.1.96 by Michael Hudson
more tests, documentation, make getBzrBranch use safe_open
140
from lp.codehosting.bzrutils import safe_open
7675.759.17 by Brad Crittenden
Reverted removal of validate_person.
141
from lp.registry.interfaces.person import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
142
    validate_person,
143
    validate_public_person,
144
    )
12640.1.1 by Robert Collins
Improve query plan for getMainlineBranchRevisions by using a second set based query to get authors rather than a single wide query.
145
from lp.services.database.bulk import load_related
7675.493.2 by Paul Hummer
Fixed an issue with upgrade_pending
146
from lp.services.job.interfaces.job import JobStatus
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
147
from lp.services.job.model.job import Job
148
from lp.services.mail.notificationrecipientset import NotificationRecipientSet
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
149
from lp.services.propertycache import cachedproperty
3311.1.5 by Mark Shuttleworth
Tighten up branch listings
150
151
9080.8.19 by Tim Penhey
Change the Branch object to inherit from the bzr identity mixin class.
152
class Branch(SQLBase, BzrIdentityMixin):
1102.1.96 by David Allouche
update sqlobject and interfaces for RevisionNumber
153
    """A sequence of ordered revisions in Bazaar."""
1102.1.52 by David Allouche
remove archbranch.py, fix branch goo until the product page works
154
11134.6.2 by Tim Penhey
More interface mangling.
155
    implements(IBranch, IBranchNavigationMenu, IPrivacy)
1102.1.52 by David Allouche
remove archbranch.py, fix branch goo until the product page works
156
    _table = 'Branch'
3814.2.4 by Tim Penhey
new branch set methods
157
4608.1.2 by Tim Penhey
More import fixes, and schema -> enum renames.
158
    branch_type = EnumCol(enum=BranchType, notNull=True)
4359.2.1 by Tim Penhey
Start of minimal explicit branch type.
159
2868.2.1 by David Allouche
[incomplete] optional-branch-title
160
    name = StringCol(notNull=False)
1102.1.55 by David Allouche
populate IBranch with easy attributes
161
    url = StringCol(dbName='url')
8503.2.1 by Paul Hummer
Re-introduced IBranch.description
162
    description = StringCol(dbName='summary')
6465.2.1 by Aaron Bentley
Scanner stores branch, repository, control formats
163
    branch_format = EnumCol(enum=BranchFormat)
164
    repository_format = EnumCol(enum=RepositoryFormat)
6465.2.15 by Aaron Bentley
Updates from review
165
    # XXX: Aaron Bentley 2008-06-13
166
    # Rename the metadir_format in the database, see bug 239746
6465.2.1 by Aaron Bentley
Scanner stores branch, repository, control formats
167
    control_format = EnumCol(enum=ControlFormat, dbName='metadir_format')
8303.16.2 by Paul Hummer
Added back in whiteboard, removed zcml for title and summary
168
    whiteboard = StringCol(default=None)
2770.1.65 by Guilherme Salgado
lots of fixes
169
    mirror_status_message = StringCol(default=None)
1102.1.55 by David Allouche
populate IBranch with easy attributes
170
4190.3.10 by Tim Penhey
moved from visibility_team to private
171
    private = BoolCol(default=False, notNull=True)
172
9941.1.3 by Tim Penhey
Let admins and bzr experts set privacy.
173
    def setPrivate(self, private, user):
8137.17.24 by Barry Warsaw
thread merge
174
        """See `IBranch`."""
175
        if private == self.private:
176
            return
9941.1.5 by Tim Penhey
Two prose text updates.
177
        # Only check the privacy policy if the user is not special.
9941.1.3 by Tim Penhey
Let admins and bzr experts set privacy.
178
        if (not user_has_special_branch_access(user)):
179
            policy = IBranchNamespacePolicy(self.namespace)
8137.17.24 by Barry Warsaw
thread merge
180
9941.1.3 by Tim Penhey
Let admins and bzr experts set privacy.
181
            if private and not policy.canBranchesBePrivate():
182
                raise BranchCannotBePrivate()
183
            if not private and not policy.canBranchesBePublic():
184
                raise BranchCannotBePublic()
8137.17.24 by Barry Warsaw
thread merge
185
        self.private = private
186
5543.6.1 by Tim Penhey
Added branch registrant.
187
    registrant = ForeignKey(
5485.1.21 by Edwin Grubbs
Added validator to Branch.registrant
188
        dbName='registrant', foreignKey='Person',
5821.2.40 by James Henstridge
* Move all the uses of public_person_validator over to the Storm
189
        storm_validator=validate_public_person, notNull=True)
5485.1.15 by Edwin Grubbs
Merged in RF
190
    owner = ForeignKey(
191
        dbName='owner', foreignKey='Person',
7675.759.17 by Brad Crittenden
Reverted removal of validate_person.
192
        storm_validator=validate_person, notNull=True)
8971.24.7 by Tim Penhey
Add implementation of Branch.setOwner.
193
194
    def setOwner(self, new_owner, user):
195
        """See `IBranch`."""
196
        new_namespace = self.target.getNamespace(new_owner)
197
        new_namespace.moveBranch(self, user, rename_if_necessary=True)
198
5280.3.4 by Tim Penhey
Getting the interface for the merge proposal to match the tests.
199
    reviewer = ForeignKey(
5485.1.13 by Edwin Grubbs
Sorta working
200
        dbName='reviewer', foreignKey='Person',
5821.2.40 by James Henstridge
* Move all the uses of public_person_validator over to the Storm
201
        storm_validator=validate_public_person, default=None)
1102.1.52 by David Allouche
remove archbranch.py, fix branch goo until the product page works
202
203
    product = ForeignKey(dbName='product', foreignKey='Product', default=None)
1102.1.55 by David Allouche
populate IBranch with easy attributes
204
7349.1.1 by Jonathan Lange
Cherry pick the content class stuff into a separate branch.
205
    distroseries = ForeignKey(
206
        dbName='distroseries', foreignKey='DistroSeries', default=None)
207
    sourcepackagename = ForeignKey(
208
        dbName='sourcepackagename', foreignKey='SourcePackageName',
209
        default=None)
210
3864.2.15 by Tim Penhey
Moved BranchLifecycleStatus to new EnumeratedTypes
211
    lifecycle_status = EnumCol(
212
        enum=BranchLifecycleStatus, notNull=True,
7675.5.1 by Tim Penhey
Remove the NEW BranchLifecycleStatus value, defaulting to DEVELOPMENT now.
213
        default=BranchLifecycleStatus.DEVELOPMENT)
1102.1.55 by David Allouche
populate IBranch with easy attributes
214
1102.1.80 by David Allouche
product/+addbranch, branch.registrant to branch.author in db patch and sample data
215
    last_mirrored = UtcDateTimeCol(default=None)
3656.1.1 by James Henstridge
db changes
216
    last_mirrored_id = StringCol(default=None)
1102.1.80 by David Allouche
product/+addbranch, branch.registrant to branch.author in db patch and sample data
217
    last_mirror_attempt = UtcDateTimeCol(default=None)
1102.1.52 by David Allouche
remove archbranch.py, fix branch goo until the product page works
218
    mirror_failures = IntCol(default=0, notNull=True)
5223.7.2 by jml at canonical
Rename mirror_request_time to next_mirror_time in code.
219
    next_mirror_time = UtcDateTimeCol(default=None)
1102.1.55 by David Allouche
populate IBranch with easy attributes
220
3656.1.1 by James Henstridge
db changes
221
    last_scanned = UtcDateTimeCol(default=None)
222
    last_scanned_id = StringCol(default=None)
3691.290.8 by Tim Penhey
tests passing for revision changes
223
    revision_count = IntCol(default=DEFAULT, notNull=True)
6125.33.5 by Jonathan Lange
Merge rename of stacked_on_branch to stacked_branch, renaming things at the
224
    stacked_on = ForeignKey(
225
        dbName='stacked_on', foreignKey='Branch', default=None)
3656.1.1 by James Henstridge
db changes
226
7675.171.1 by Tim Penhey
Add attributes for the new branch columns.
227
    # The unique_name is maintined by a SQL trigger.
228
    unique_name = StringCol()
229
    # Denormalised colums used primarily for sorting.
230
    owner_name = StringCol()
231
    target_suffix = StringCol()
232
4652.1.4 by Jonathan Lange
Make mirror_request_time the determining variable for mirrored branches
233
    def __repr__(self):
234
        return '<Branch %r (%d)>' % (self.unique_name, self.id)
235
3691.410.2 by David Allouche
adjust database.Branch to deal with RevisionNumber where sequence is NULL
236
    @property
7779.1.5 by Jonathan Lange
Change the property from 'container' to 'target'
237
    def target(self):
7362.4.13 by Jonathan Lange
Add a basic context variable, call it "container" to avoid decorates() confusion.
238
        """See `IBranch`."""
239
        if self.product is None:
240
            if self.distroseries is None:
7779.1.7 by Jonathan Lange
Use the adapter, simplifying the code a little.
241
                target = self.owner
7362.4.13 by Jonathan Lange
Add a basic context variable, call it "container" to avoid decorates() confusion.
242
            else:
7779.1.7 by Jonathan Lange
Use the adapter, simplifying the code a little.
243
                target = self.sourcepackage
7362.4.13 by Jonathan Lange
Add a basic context variable, call it "container" to avoid decorates() confusion.
244
        else:
7779.1.7 by Jonathan Lange
Use the adapter, simplifying the code a little.
245
            target = self.product
246
        return IBranchTarget(target)
7362.4.13 by Jonathan Lange
Add a basic context variable, call it "container" to avoid decorates() confusion.
247
8971.24.14 by Tim Penhey
Make setTarget actually exportable.
248
    def setTarget(self, user, project=None, source_package=None):
8971.24.8 by Tim Penhey
Add setTarget for branch.
249
        """See `IBranch`."""
8971.24.14 by Tim Penhey
Make setTarget actually exportable.
250
        if project is not None:
251
            if source_package is not None:
252
                raise BranchTargetError(
253
                    'Cannot specify both a project and a source package.')
254
            else:
255
                target = IBranchTarget(project)
256
                if target is None:
257
                    raise BranchTargetError(
258
                        '%r is not a valid project target' % project)
259
        elif source_package is not None:
260
            target = IBranchTarget(source_package)
8971.24.8 by Tim Penhey
Add setTarget for branch.
261
            if target is None:
8971.24.14 by Tim Penhey
Make setTarget actually exportable.
262
                raise BranchTargetError(
9222.5.4 by Aaron Bentley
Fix lint errors.
263
                    '%r is not a valid source package target' %
264
                    source_package)
8971.24.14 by Tim Penhey
Make setTarget actually exportable.
265
        else:
266
            target = IBranchTarget(self.owner)
267
            # Person targets are always valid.
268
        namespace = target.getNamespace(self.owner)
269
        namespace.moveBranch(self, user, rename_if_necessary=True)
8971.24.8 by Tim Penhey
Add setTarget for branch.
270
7362.4.13 by Jonathan Lange
Add a basic context variable, call it "container" to avoid decorates() confusion.
271
    @property
7847.1.18 by Jonathan Lange
Add a 'namespace' attribute to Branch that returns the branch's namespace.
272
    def namespace(self):
273
        """See `IBranch`."""
274
        return self.target.getNamespace(self.owner)
275
276
    @property
7362.13.2 by Jonathan Lange
Add a distribution property to IBranch.
277
    def distribution(self):
278
        """See `IBranch`."""
279
        if self.distroseries is None:
280
            return None
281
        return self.distroseries.distribution
282
283
    @property
7362.13.4 by Jonathan Lange
Add a sourcepackage property.
284
    def sourcepackage(self):
285
        """See `IBranch`."""
286
        # Avoid circular imports.
7675.110.3 by Curtis Hovey
Ran the migration script to move registry code to lp.registry.
287
        from lp.registry.model.sourcepackage import SourcePackage
7362.13.4 by Jonathan Lange
Add a sourcepackage property.
288
        if self.distroseries is None:
289
            return None
290
        return SourcePackage(self.sourcepackagename, self.distroseries)
291
292
    @property
3691.410.2 by David Allouche
adjust database.Branch to deal with RevisionNumber where sequence is NULL
293
    def revision_history(self):
7675.747.1 by Jeroen Vermeulen
Intermediate state: stormifying BranchRevision.
294
        result = Store.of(self).find(
7675.747.10 by Jeroen Vermeulen
Lint.
295
            (BranchRevision, Revision),
7675.747.1 by Jeroen Vermeulen
Intermediate state: stormifying BranchRevision.
296
            BranchRevision.branch_id == self.id,
7675.747.10 by Jeroen Vermeulen
Lint.
297
            Revision.id == BranchRevision.revision_id,
7675.747.1 by Jeroen Vermeulen
Intermediate state: stormifying BranchRevision.
298
            BranchRevision.sequence != None)
7675.747.10 by Jeroen Vermeulen
Lint.
299
        result = result.order_by(Desc(BranchRevision.sequence))
11822.4.3 by Robert Collins
Really drop last uses of prejoin.
300
        return DecoratedResultSet(result, operator.itemgetter(0))
3691.410.2 by David Allouche
adjust database.Branch to deal with RevisionNumber where sequence is NULL
301
3226.2.1 by Diogo Matsubara
Fix https://launchpad.net/products/launchpad/+bug/33625 (Change MultipleJoin to use the new SQLMultipleJoin.)
302
    subscriptions = SQLMultipleJoin(
1102.1.85 by David Allouche
can subscribe to and unsubscribe from branch
303
        'BranchSubscription', joinColumn='branch', orderBy='id')
3504.1.13 by kiko
Implement initial SQLRelatedJoin migration across Launchpad tree. Still needs to reconsider the Snapshot approach which will be a big performance hit.
304
    subscribers = SQLRelatedJoin(
1102.1.85 by David Allouche
can subscribe to and unsubscribe from branch
305
        'Person', joinColumn='branch', otherColumn='person',
306
        intermediateTable='BranchSubscription', orderBy='name')
307
3283.3.3 by Brad Bollenbach
pair code reviewing with lifeless
308
    bug_branches = SQLMultipleJoin(
309
        'BugBranch', joinColumn='branch', orderBy='id')
310
8752.4.13 by Paul Hummer
Fixed test with new bug branch removal
311
    linked_bugs = SQLRelatedJoin(
312
        'Bug', joinColumn='branch', otherColumn='bug',
313
        intermediateTable='BugBranch', orderBy='id')
8752.4.12 by Paul Hummer
Added IBranch.linked_bugs
314
12505.6.1 by Ian Booth
Remove DecoratedBug and refactor mp linked_bugs to use branch.getRelatedBugTasks
315
    def getLinkedBugTasks(self, user, status_filter=None):
12457.2.3 by Robert Collins
Lock down scaling of branch:+index for product bugtasks - still to do productseries and distroseries.
316
        """See `IBranch`."""
317
        params = BugTaskSearchParams(user=user, linked_branches=self.id,
318
            status=status_filter)
12457.2.6 by Robert Collins
Review feedback: use a shortlist, don't spread the any pollution of default symbols. Docstring fixup and unbreak the legacy linked_bugs.
319
        tasks = shortlist(getUtility(IBugTaskSet).search(params), 1000)
12457.2.3 by Robert Collins
Lock down scaling of branch:+index for product bugtasks - still to do productseries and distroseries.
320
        # Post process to discard irrelevant tasks: we only return one task per
321
        # bug, and cannot easily express this in sql (yet).
12588.3.21 by Tim Penhey
Remove branch specific bug task filtering, and add the possibility of titles to the generic link formatter.
322
        return filter_bugtasks_by_context(self.target.context, tasks)
10974.1.2 by Tim Penhey
Add a method to get all the bugtasks for the linked bugs at one go.
323
8752.4.4 by Paul Hummer
Fixed the linking tests
324
    def linkBug(self, bug, registrant):
325
        """See `IBranch`."""
326
        return bug.linkBranch(self, registrant)
327
8752.4.5 by Paul Hummer
Fixed the unlinking tests
328
    def unlinkBug(self, bug, user):
329
        """See `IBranch`."""
330
        return bug.unlinkBranch(self, user)
331
3691.233.11 by James Henstridge
add a similar portlet to the branch index page
332
    spec_links = SQLMultipleJoin('SpecificationBranch',
333
        joinColumn='branch',
334
        orderBy='id')
335
8752.4.4 by Paul Hummer
Fixed the linking tests
336
    def linkSpecification(self, spec, registrant):
337
        """See `IBranch`."""
338
        return spec.linkBranch(self, registrant)
339
8752.4.5 by Paul Hummer
Fixed the unlinking tests
340
    def unlinkSpecification(self, spec, user):
341
        """See `IBranch`."""
342
        return spec.unlinkBranch(self, user)
343
3754.2.8 by Tim Penhey
review comment fixes
344
    date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
5001.2.3 by Tim Penhey
More test details.
345
    date_last_modified = UtcDateTimeCol(notNull=True, default=DEFAULT)
3754.2.8 by Tim Penhey
review comment fixes
346
4414.6.6 by Tim Penhey
More moves and changes.
347
    landing_targets = SQLMultipleJoin(
4414.5.13 by Tim Penhey
Still work in progress
348
        'BranchMergeProposal', joinColumn='source_branch')
4414.6.8 by Tim Penhey
Added two necessary fields.
349
350
    @property
9222.5.6 by Aaron Bentley
Don't update preview diffs for merge proposals in final states.
351
    def active_landing_targets(self):
352
        """Merge proposals not in final states where this branch is source."""
353
        store = Store.of(self)
354
        return store.find(
355
            BranchMergeProposal, BranchMergeProposal.source_branch == self,
356
            Not(BranchMergeProposal.queue_status.is_in(
357
                BRANCH_MERGE_PROPOSAL_FINAL_STATES)))
358
359
    @property
4414.6.8 by Tim Penhey
Added two necessary fields.
360
    def landing_candidates(self):
361
        """See `IBranch`."""
5600.2.2 by Tim Penhey
Resubmit merge proposals.
362
        return BranchMergeProposal.select("""
363
            BranchMergeProposal.target_branch = %s AND
364
            BranchMergeProposal.queue_status NOT IN %s
365
            """ % sqlvalues(self, BRANCH_MERGE_PROPOSAL_FINAL_STATES))
4414.6.6 by Tim Penhey
More moves and changes.
366
4414.5.13 by Tim Penhey
Still work in progress
367
    @property
368
    def dependent_branches(self):
369
        """See `IBranch`."""
5600.2.2 by Tim Penhey
Resubmit merge proposals.
370
        return BranchMergeProposal.select("""
371
            BranchMergeProposal.dependent_branch = %s AND
372
            BranchMergeProposal.queue_status NOT IN %s
373
            """ % sqlvalues(self, BRANCH_MERGE_PROPOSAL_FINAL_STATES))
4414.5.13 by Tim Penhey
Still work in progress
374
11929.5.1 by Aaron Bentley
Support getMergeProposals with merged_revno.
375
    def getMergeProposals(self, status=None, visible_by_user=None,
11929.5.2 by Aaron Bentley
Switch from single-revno UI to multi-revno.
376
                          merged_revnos=None):
11929.5.3 by Aaron Bentley
Update docs.
377
        """See `IBranch`."""
9269.3.17 by Tim Penhey
Make a branch implement IHasMergeProposals for proposals that target it.
378
        if not status:
379
            status = (
380
                BranchMergeProposalStatus.CODE_APPROVED,
381
                BranchMergeProposalStatus.NEEDS_REVIEW,
382
                BranchMergeProposalStatus.WORK_IN_PROGRESS)
383
384
        collection = getUtility(IAllBranches).visibleByUser(visible_by_user)
11929.5.1 by Aaron Bentley
Support getMergeProposals with merged_revno.
385
        return collection.getMergeProposals(
11929.5.2 by Aaron Bentley
Switch from single-revno UI to multi-revno.
386
            status, target_branch=self, merged_revnos=merged_revnos)
9269.3.17 by Tim Penhey
Make a branch implement IHasMergeProposals for proposals that target it.
387
8377.8.3 by Tim Penhey
Add a branch level method that delegates to the branch target for now.
388
    def isBranchMergeable(self, target_branch):
389
        """See `IBranch`."""
390
        # In some imaginary time we may actually check to see if this branch
391
        # and the target branch have common ancestry.
392
        return self.target.areBranchesMergeable(target_branch.target)
393
4414.6.7 by Tim Penhey
More tests.
394
    def addLandingTarget(self, registrant, target_branch,
7675.343.1 by Aaron Bentley
Rename dependent branch to prerequisite branch.
395
                         prerequisite_branch=None, whiteboard=None,
7325.5.11 by Aaron Bentley
Ensure BMP.review_diff is attached to the MailController
396
                         date_created=None, needs_review=False,
7675.548.9 by Tim Penhey
Stop pretending it is an initial comment.
397
                         description=None, review_requests=None,
11542.3.20 by Ian Booth
Test refactoring as per code review
398
                         review_diff=None, commit_message=None):
4414.6.6 by Tim Penhey
More moves and changes.
399
        """See `IBranch`."""
8377.8.13 by Tim Penhey
Fix the checks in addLandingTarget.
400
        if not self.target.supports_merge_proposals:
401
            raise InvalidBranchMergeProposal(
402
                '%s branches do not support merge proposals.'
403
                % self.target.displayname)
4414.6.6 by Tim Penhey
More moves and changes.
404
        if self == target_branch:
405
            raise InvalidBranchMergeProposal(
406
                'Source and target branches must be different.')
8377.8.13 by Tim Penhey
Fix the checks in addLandingTarget.
407
        if not target_branch.isBranchMergeable(self):
4414.6.6 by Tim Penhey
More moves and changes.
408
            raise InvalidBranchMergeProposal(
8377.8.13 by Tim Penhey
Fix the checks in addLandingTarget.
409
                '%s is not mergeable into %s' % (
8377.8.17 by Tim Penhey
Make the displayname the bzr_identity.
410
                    self.displayname, target_branch.displayname))
7675.343.1 by Aaron Bentley
Rename dependent branch to prerequisite branch.
411
        if prerequisite_branch is not None:
412
            if not self.isBranchMergeable(prerequisite_branch):
8377.8.13 by Tim Penhey
Fix the checks in addLandingTarget.
413
                raise InvalidBranchMergeProposal(
414
                    '%s is not mergeable into %s' % (
7675.343.1 by Aaron Bentley
Rename dependent branch to prerequisite branch.
415
                        prerequisite_branch.displayname, self.displayname))
416
            if self == prerequisite_branch:
417
                raise InvalidBranchMergeProposal(
418
                    'Source and prerequisite branches must be different.')
419
            if target_branch == prerequisite_branch:
420
                raise InvalidBranchMergeProposal(
421
                    'Target and prerequisite branches must be different.')
4414.6.6 by Tim Penhey
More moves and changes.
422
8511.2.2 by Aaron Bentley
Prevent activating a proposal when an active duplicate exists.
423
        target = BranchMergeProposalGetter.activeProposalsForBranches(
424
            self, target_branch)
5600.2.1 by Tim Penhey
State transition test complete.
425
        if target.count() > 0:
7573.2.2 by Paul Hummer
Added try/except block, catching the oops
426
            raise BranchMergeProposalExists(
4414.6.6 by Tim Penhey
More moves and changes.
427
                'There is already a branch merge proposal registered for '
5600.2.1 by Tim Penhey
State transition test complete.
428
                'branch %s to land on %s that is still active.'
8377.8.17 by Tim Penhey
Make the displayname the bzr_identity.
429
                % (self.displayname, target_branch.displayname))
4414.6.6 by Tim Penhey
More moves and changes.
430
4414.6.8 by Tim Penhey
Added two necessary fields.
431
        if date_created is None:
432
            date_created = UTC_NOW
5001.2.10 by Tim Penhey
Missing zcml attribute, typo and login required on a doc test.
433
6623.3.3 by Tim Penhey
Updates following review.
434
        if needs_review:
435
            queue_status = BranchMergeProposalStatus.NEEDS_REVIEW
7074.2.1 by Tim Penhey
Change the register a merge proposal to be a form where the user can specify an initial comment and request a reviewer.
436
            date_review_requested = date_created
6623.3.3 by Tim Penhey
Updates following review.
437
        else:
438
            queue_status = BranchMergeProposalStatus.WORK_IN_PROGRESS
7074.2.1 by Tim Penhey
Change the register a merge proposal to be a form where the user can specify an initial comment and request a reviewer.
439
            date_review_requested = None
6623.3.3 by Tim Penhey
Updates following review.
440
7334.4.18 by Tim Penhey
Updates following review comments.
441
        if review_requests is None:
442
            review_requests = []
7334.4.4 by Tim Penhey
Add the ability to set the initial comment and requested reviewers when adding the landing target to the branch.
443
11542.3.23 by Ian Booth
Minor fixes
444
        # If no reviewer is specified, use the default for the branch.
445
        if len(review_requests) == 0:
446
            review_requests.append((target_branch.code_reviewer, None))
11542.3.1 by Ian Booth
Initial core code changes
447
5608.5.5 by Aaron Bentley
Add notification on BranchMergeProposal creation
448
        bmp = BranchMergeProposal(
4414.6.6 by Tim Penhey
More moves and changes.
449
            registrant=registrant, source_branch=self,
7675.343.1 by Aaron Bentley
Rename dependent branch to prerequisite branch.
450
            target_branch=target_branch,
451
            prerequisite_branch=prerequisite_branch, whiteboard=whiteboard,
452
            date_created=date_created,
7074.2.1 by Tim Penhey
Change the register a merge proposal to be a form where the user can specify an initial comment and request a reviewer.
453
            date_review_requested=date_review_requested,
9801.1.4 by Aaron Bentley
Expose commit_message throughout the stack.
454
            queue_status=queue_status, review_diff=review_diff,
7675.548.2 by Tim Penhey
Update for the description field.
455
            commit_message=commit_message,
7675.548.9 by Tim Penhey
Stop pretending it is an initial comment.
456
            description=description)
7334.4.4 by Tim Penhey
Add the ability to set the initial comment and requested reviewers when adding the landing target to the branch.
457
7334.4.18 by Tim Penhey
Updates following review comments.
458
        for reviewer, review_type in review_requests:
7334.4.4 by Tim Penhey
Add the ability to set the initial comment and requested reviewers when adding the landing target to the branch.
459
            bmp.nominateReviewer(
460
                reviewer, registrant, review_type, _notify_listeners=False)
461
462
        notify(NewBranchMergeProposalEvent(bmp))
11733.1.5 by Tim Penhey
Raise the needs review event when created in needs review or going from work in progress to needs review.
463
        if needs_review:
464
            notify(BranchMergeProposalNeedsReviewEvent(bmp))
11733.1.4 by Tim Penhey
Add an extra event for the needs review, but keep the same functionality for now.
465
5608.5.5 by Aaron Bentley
Add notification on BranchMergeProposal creation
466
        return bmp
4414.6.6 by Tim Penhey
More moves and changes.
467
9801.1.6 by Aaron Bentley
Allow creating merge proposal with reviewers via API.
468
    def _createMergeProposal(
469
        self, registrant, target_branch, prerequisite_branch=None,
470
        needs_review=True, initial_comment=None, commit_message=None,
471
        reviewers=None, review_types=None):
9801.1.7 by Aaron Bentley
Add comment.
472
        """See `IBranch`."""
9801.1.6 by Aaron Bentley
Allow creating merge proposal with reviewers via API.
473
        if reviewers is None:
474
            reviewers = []
475
        if review_types is None:
476
            review_types = []
477
        if len(reviewers) != len(review_types):
478
            raise ValueError(
479
                'reviewers and review_types must be equal length.')
480
        review_requests = zip(reviewers, review_types)
10466.1.5 by Paul Hummer
Better solution!
481
        return self.addLandingTarget(
482
            registrant, target_branch, prerequisite_branch,
483
            needs_review=needs_review, description=initial_comment,
484
            commit_message=commit_message, review_requests=review_requests)
9801.1.6 by Aaron Bentley
Allow creating merge proposal with reviewers via API.
485
9222.5.1 by Aaron Bentley
Provide for scheduling diff updates.
486
    def scheduleDiffUpdates(self):
487
        """See `IBranch`."""
11486.4.19 by Aaron Bentley
Create GenerateIncrementalDiff jobs missing incremental diffs on tip change.
488
        from lp.code.model.branchmergeproposaljob import (
489
                GenerateIncrementalDiffJob,
490
                UpdatePreviewDiffJob,
11486.4.36 by Aaron Bentley
Updates from review.
491
            )
11486.4.19 by Aaron Bentley
Create GenerateIncrementalDiff jobs missing incremental diffs on tip change.
492
        jobs = []
493
        for merge_proposal in self.active_landing_targets:
494
            if merge_proposal.target_branch.last_scanned_id is None:
495
                continue
496
            jobs.append(UpdatePreviewDiffJob.create(merge_proposal))
497
            for old, new in merge_proposal.getMissingIncrementalDiffs():
498
                GenerateIncrementalDiffJob.create(
499
                    merge_proposal, old.revision_id, new.revision_id)
9222.5.1 by Aaron Bentley
Provide for scheduling diff updates.
500
        return jobs
501
7362.11.41 by Jonathan Lange
Add an addToLaunchBag method.
502
    def addToLaunchBag(self, launchbag):
503
        """See `IBranch`."""
504
        launchbag.add(self.product)
505
        if self.distroseries is not None:
506
            launchbag.add(self.distroseries)
7362.13.3 by Jonathan Lange
Use distribution property more effectively.
507
            launchbag.add(self.distribution)
7362.13.5 by Jonathan Lange
Some cleanups, now that we have a source package property.
508
            launchbag.add(self.sourcepackage)
7362.11.41 by Jonathan Lange
Add an addToLaunchBag method.
509
6983.2.1 by Jonathan Lange
Add a getStackedBranches method to IBranch.
510
    def getStackedBranches(self):
511
        """See `IBranch`."""
6983.2.2 by Jonathan Lange
Give getStackedBranches a working implementation.
512
        store = Store.of(self)
513
        return store.find(Branch, Branch.stacked_on == self)
6983.2.1 by Jonathan Lange
Add a getStackedBranches method to IBranch.
514
1102.1.69 by David Allouche
link to branch page from branch-summary-listing
515
    @property
4682.2.4 by Tim Penhey
Updates following review comments
516
    def code_is_browseable(self):
517
        """See `IBranch`."""
7675.747.9 by Jeroen Vermeulen
Lint.
518
        return (self.revision_count > 0 or self.last_mirrored != None)
7622.1.2 by Michael Hudson
some progress
519
520
    def codebrowse_url(self, *extras):
521
        """See `IBranch`."""
522
        if self.private:
523
            root = config.codehosting.secure_codebrowse_root
524
        else:
525
            root = config.codehosting.codebrowse_root
526
        return urlutils.join(root, self.unique_name, *extras)
4813.8.1 by Tim Penhey
Lots of url moving on the branch index page.
527
5805.2.1 by Tim Penhey
Initial work.
528
    @property
9643.1.1 by Jonathan Lange
Add a browse_source_url property to branch that points to the URL to browse
529
    def browse_source_url(self):
530
        return self.codebrowse_url('files')
531
10312.5.5 by Jeroen Vermeulen
IBranch.composePublicURL.
532
    def composePublicURL(self, scheme='http'):
533
        """See `IBranch`."""
534
        # Not all protocols work for private branches.
535
        public_schemes = ['http']
536
        assert not (self.private and scheme in public_schemes), (
537
            "Private branch %s has no public URL." % self.unique_name)
10312.8.1 by Aaron Bentley
Refactor Branch.composePublicUrl and getUniqueNameResultDict
538
        return compose_public_url(scheme, self.unique_name)
10312.5.5 by Jeroen Vermeulen
IBranch.composePublicURL.
539
9590.1.104 by Michael Hudson
test_codehandler tests now pass
540
    def getInternalBzrUrl(self):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
541
        """See `IBranch`."""
9590.1.104 by Michael Hudson
test_codehandler tests now pass
542
        return 'lp-internal:///' + self.unique_name
3335.2.4 by David Allouche
review comments and test fixes
543
6805.13.71 by Aaron Bentley
Rename diff.StaticDiffJob to branch.BranchDiffJob
544
    def getBzrBranch(self):
9590.1.89 by Michael Hudson
add getBzrBranch to the interface
545
        """See `IBranch`."""
9590.1.104 by Michael Hudson
test_codehandler tests now pass
546
        return safe_open('lp-internal', self.getInternalBzrUrl())
6805.13.71 by Aaron Bentley
Rename diff.StaticDiffJob to branch.BranchDiffJob
547
3249.5.1 by David Allouche
Branch pages title fixes
548
    @property
549
    def displayname(self):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
550
        """See `IBranch`."""
8377.8.17 by Tim Penhey
Make the displayname the bzr_identity.
551
        return self.bzr_identity
3249.5.1 by David Allouche
Branch pages title fixes
552
3412.1.15 by David Allouche
factor out and document Branch.sort_key
553
    @property
5280.4.25 by Tim Penhey
Updates following review.
554
    def code_reviewer(self):
555
        """See `IBranch`."""
556
        if self.reviewer:
557
            return self.reviewer
558
        else:
559
            return self.owner
560
9041.4.1 by Tim Penhey
Move isPersonTrustedReviewer to the IBranch interface.
561
    def isPersonTrustedReviewer(self, reviewer):
562
        """See `IBranch`."""
563
        if reviewer is None:
564
            return False
565
        # We trust Launchpad admins.
566
        lp_admins = getUtility(ILaunchpadCelebrities).admin
567
        # Both the branch owner and the review team are checked.
568
        owner = self.owner
569
        review_team = self.code_reviewer
570
        return (
571
            reviewer.inTeam(owner) or
572
            reviewer.inTeam(review_team) or
573
            reviewer.inTeam(lp_admins))
574
1102.1.73 by David Allouche
branch portlets and revision list limit
575
    def latest_revisions(self, quantity=10):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
576
        """See `IBranch`."""
7675.747.1 by Jeroen Vermeulen
Intermediate state: stormifying BranchRevision.
577
        return self.revision_history.config(limit=quantity)
1102.1.73 by David Allouche
branch portlets and revision list limit
578
9984.4.5 by Tim Penhey
New storm style method to get mainline branch revisions.
579
    def getMainlineBranchRevisions(self, start_date, end_date=None,
580
                                   oldest_first=False):
581
        """See `IBranch`."""
582
        date_clause = Revision.revision_date >= start_date
583
        if end_date is not None:
584
            date_clause = And(date_clause, Revision.revision_date <= end_date)
585
        result = Store.of(self).find(
12640.1.1 by Robert Collins
Improve query plan for getMainlineBranchRevisions by using a second set based query to get authors rather than a single wide query.
586
            (BranchRevision, Revision),
9984.4.5 by Tim Penhey
New storm style method to get mainline branch revisions.
587
            BranchRevision.branch == self,
588
            BranchRevision.sequence != None,
589
            BranchRevision.revision == Revision.id,
590
            date_clause)
591
        if oldest_first:
592
            result = result.order_by(BranchRevision.sequence)
593
        else:
594
            result = result.order_by(Desc(BranchRevision.sequence))
12640.1.1 by Robert Collins
Improve query plan for getMainlineBranchRevisions by using a second set based query to get authors rather than a single wide query.
595
        def eager_load(rows):
596
            revisions = map(operator.itemgetter(1), rows)
597
            load_related(RevisionAuthor, revisions, ['revision_author_id'])
598
        return DecoratedResultSet(result, pre_iter_hook=eager_load)
9984.4.5 by Tim Penhey
New storm style method to get mainline branch revisions.
599
9984.4.1 by Tim Penhey
Rename revisions_since to follow the LP coding standard.
600
    def getRevisionsSince(self, timestamp):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
601
        """See `IBranch`."""
7675.747.12 by Jeroen Vermeulen
One more prejoin, as per reviewer.
602
        result = Store.of(self).find(
603
            (BranchRevision, Revision),
7675.747.2 by Jeroen Vermeulen
Stormified rest of BranchRevision usage.
604
            Revision.id == BranchRevision.revision_id,
7675.747.1 by Jeroen Vermeulen
Intermediate state: stormifying BranchRevision.
605
            BranchRevision.branch == self,
606
            BranchRevision.sequence != None,
607
            Revision.revision_date > timestamp)
7675.747.12 by Jeroen Vermeulen
One more prejoin, as per reviewer.
608
        result = result.order_by(Desc(BranchRevision.sequence))
609
        # Return BranchRevision but prejoin Revision as well.
11822.4.3 by Robert Collins
Really drop last uses of prejoin.
610
        return DecoratedResultSet(result, operator.itemgetter(0))
1102.1.121 by david
show author of revisions and number of revisions in the last 30 days
611
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
612
    def canBeDeleted(self):
613
        """See `IBranch`."""
7735.1.1 by Tim Penhey
Don't allow branches with other branches stacked on them to be deleted.
614
        if ((len(self.deletionRequirements()) != 0) or
615
            self.getStackedBranches().count() > 0):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
616
            # Can't delete if the branch is associated with anything.
617
            return False
618
        else:
619
            return True
1102.1.52 by David Allouche
remove archbranch.py, fix branch goo until the product page works
620
12393.27.5 by Danilo Segan
Merge with latest accordionoverlay.
621
    @cachedproperty
5616.2.14 by Aaron Bentley
Updates from tests
622
    def code_import(self):
8138.1.2 by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing.
623
        from lp.code.model.codeimport import CodeImportSet
5616.2.14 by Aaron Bentley
Updates from tests
624
        return CodeImportSet().getByBranch(self)
625
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
626
    def _deletionRequirements(self):
5616.2.12 by Aaron Bentley
Document eeeverything
627
        """Determine what operations must be performed to delete this branch.
628
629
        Two dictionaries are returned, one for items that must be deleted,
630
        one for items that must be altered.  The item in question is the
631
        key, and the value is a user-facing string explaining why the item
632
        is affected.
633
5616.2.40 by Aaron Bentley
Update docs
634
        As well as the dictionaries, this method returns two list of callables
635
        that may be called to perform the alterations and deletions needed.
5616.2.12 by Aaron Bentley
Document eeeverything
636
        """
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
637
        alteration_operations = []
5616.2.26 by Aaron Bentley
Do object deletion via callbacks
638
        deletion_operations = []
5616.2.13 by Aaron Bentley
Tweak comments
639
        # Merge proposals require their source and target branches to exist.
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
640
        for merge_proposal in self.landing_targets:
6019.2.1 by Aaron Bentley
Use objects instead of closures for deletion
641
            deletion_operations.append(
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
642
                DeletionCallable(merge_proposal,
6019.2.4 by Aaron Bentley
Move affected_object, rationale into into DeletionOperation
643
                    _('This branch is the source branch of this merge'
6475.2.22 by Barry Warsaw
mergeRF
644
                    ' proposal.'), merge_proposal.deleteProposal))
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
645
        # Cannot use self.landing_candidates, because it ignores merged
5616.2.13 by Aaron Bentley
Tweak comments
646
        # merge proposals.
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
647
        for merge_proposal in BranchMergeProposal.selectBy(
648
            target_branch=self):
6019.2.2 by Aaron Bentley
Use DeletionOperation everywhere
649
            deletion_operations.append(
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
650
                DeletionCallable(merge_proposal,
6019.2.4 by Aaron Bentley
Move affected_object, rationale into into DeletionOperation
651
                    _('This branch is the target branch of this merge'
6475.2.22 by Barry Warsaw
mergeRF
652
                    ' proposal.'), merge_proposal.deleteProposal))
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
653
        for merge_proposal in BranchMergeProposal.selectBy(
7675.343.1 by Aaron Bentley
Rename dependent branch to prerequisite branch.
654
            prerequisite_branch=self):
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
655
            alteration_operations.append(ClearDependentBranch(merge_proposal))
656
5616.2.2 by Aaron Bentley
Support deleting subscriptions and bug branches
657
        for bugbranch in self.bug_branches:
6019.2.2 by Aaron Bentley
Use DeletionOperation everywhere
658
            deletion_operations.append(
10362.2.3 by Paul Hummer
Removed the ugly tal change.
659
                DeletionCallable(bugbranch.bug.default_bugtask,
6019.2.4 by Aaron Bentley
Move affected_object, rationale into into DeletionOperation
660
                _('This bug is linked to this branch.'),
661
                bugbranch.destroySelf))
5616.2.4 by Aaron Bentley
Handle spec subscriptions when deleting branches
662
        for spec_link in self.spec_links:
6019.2.2 by Aaron Bentley
Use DeletionOperation everywhere
663
            deletion_operations.append(
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
664
                DeletionCallable(spec_link,
6019.2.4 by Aaron Bentley
Move affected_object, rationale into into DeletionOperation
665
                    _('This blueprint is linked to this branch.'),
666
                    spec_link.destroySelf))
5616.2.6 by Aaron Bentley
Handle branch deletion for branches associated with series
667
        for series in self.associatedProductSeries():
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
668
            alteration_operations.append(ClearSeriesBranch(series, self))
8771.6.1 by Jeroen Vermeulen
UI for exporting translations to bzr branch.
669
        for series in self.getProductSeriesPushingTranslations():
670
            alteration_operations.append(
671
                ClearSeriesTranslationsBranch(series, self))
8211.4.12 by Jonathan Lange
Allow branches linked to source packages to be deleted.
672
8211.4.14 by Jonathan Lange
Further split the interfaces, correct the permissions.
673
        series_set = getUtility(IFindOfficialBranchLinks)
8211.4.12 by Jonathan Lange
Allow branches linked to source packages to be deleted.
674
        alteration_operations.extend(
675
            map(ClearOfficialPackageBranch, series_set.findForBranch(self)))
10498.5.36 by Aaron Bentley
Deleting branches used in recipes deletes the recipes.
676
        deletion_operations.extend(
677
            DeletionCallable.forSourcePackageRecipe(recipe)
12397.2.8 by Ian Booth
Change from using getter methods to properties for exported recipe and build accessors
678
            for recipe in self.recipes)
6019.2.4 by Aaron Bentley
Move affected_object, rationale into into DeletionOperation
679
        return (alteration_operations, deletion_operations)
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
680
681
    def deletionRequirements(self):
5616.2.12 by Aaron Bentley
Document eeeverything
682
        """See `IBranch`."""
6019.2.4 by Aaron Bentley
Move affected_object, rationale into into DeletionOperation
683
        alteration_operations, deletion_operations, = (
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
684
            self._deletionRequirements())
6019.2.4 by Aaron Bentley
Move affected_object, rationale into into DeletionOperation
685
        result = dict(
686
            (operation.affected_object, ('alter', operation.rationale)) for
687
            operation in alteration_operations)
5616.2.13 by Aaron Bentley
Tweak comments
688
        # Deletion entries should overwrite alteration entries.
6019.2.4 by Aaron Bentley
Move affected_object, rationale into into DeletionOperation
689
        result.update(
690
            (operation.affected_object, ('delete', operation.rationale)) for
691
            operation in deletion_operations)
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
692
        return result
693
694
    def _breakReferences(self):
5616.2.12 by Aaron Bentley
Document eeeverything
695
        """Break all external references to this branch.
696
697
        NULLable references will be NULLed.  References which are not NULLable
698
        will cause the item holding the reference to be deleted.
699
700
        This function is guaranteed to perform the operations predicted by
701
        deletionRequirements, because it uses the same backing function.
702
        """
6019.2.4 by Aaron Bentley
Move affected_object, rationale into into DeletionOperation
703
        (alteration_operations,
5616.2.36 by Aaron Bentley
Fix lint errors
704
            deletion_operations) = self._deletionRequirements()
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
705
        for operation in alteration_operations:
6019.2.10 by Aaron Bentley
Updatest from review
706
            operation()
5616.2.26 by Aaron Bentley
Do object deletion via callbacks
707
        for operation in deletion_operations:
6019.2.10 by Aaron Bentley
Updatest from review
708
            operation()
9636.5.15 by Jonathan Lange
Make it possible to delete the code import branch if you own it.
709
        # Special-case code import, since users don't have lp.Edit on them,
710
        # since if you can delete a branch you should be able to delete the
711
        # code import and since deleting the code import object itself isn't
712
        # actually a very interesting thing to tell the user about.
713
        if self.code_import is not None:
714
            DeleteCodeImport(self.code_import)()
11316.6.2 by Tim Penhey
The translation clearing class didn't do what it said, and it wasn't tested.
715
        Store.of(self).flush()
5616.2.1 by Aaron Bentley
Allow branches with merge proposals to be deleted
716
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
717
    @cachedproperty
718
    def _associatedProductSeries(self):
719
        """Helper for eager loading associatedProductSeries."""
720
        # This is eager loaded by BranchCollection.getBranches.
721
        # Imported here to avoid circular import.
722
        from lp.registry.model.productseries import ProductSeries
723
        return Store.of(self).find(
724
            ProductSeries,
725
            ProductSeries.branch == self)
726
12508.1.1 by William Grant
Revert r12505 and 12489. The former was meant to fix the latter, but did not.
727
    def associatedProductSeries(self):
728
        """See `IBranch`."""
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
729
        return self._associatedProductSeries
12475.1.4 by Robert Collins
Eager load associated product series for getBranches.
730
8771.6.1 by Jeroen Vermeulen
UI for exporting translations to bzr branch.
731
    def getProductSeriesPushingTranslations(self):
732
        """See `IBranch`."""
733
        # Imported here to avoid circular import.
734
        from lp.registry.model.productseries import ProductSeries
735
        return Store.of(self).find(
736
            ProductSeries,
737
            ProductSeries.translations_branch == self)
738
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
739
    @cachedproperty
740
    def _associatedSuiteSourcePackages(self):
741
        """Helper for associatedSuiteSourcePackages."""
742
        # This is eager loaded by BranchCollection.getBranches.
8852.2.1 by Tim Penhey
Make the decorated branch listing item pass itself through when determining the bazaar_identity so the cached associated product series can be used.
743
        series_set = getUtility(IFindOfficialBranchLinks)
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
744
        # Order by the pocket to get the release one first. If changing this be
745
        # sure to also change BranchCollection.getBranches.
8852.2.1 by Tim Penhey
Make the decorated branch listing item pass itself through when determining the bazaar_identity so the cached associated product series can be used.
746
        links = series_set.findForBranch(self).order_by(
747
            SeriesSourcePackageBranch.pocket)
748
        return [link.suite_sourcepackage for link in links]
749
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
750
    def associatedSuiteSourcePackages(self):
751
        """See `IBranch`."""
752
        return self._associatedSuiteSourcePackages
753
1102.1.85 by David Allouche
can subscribe to and unsubscribe from branch
754
    # subscriptions
5608.5.1 by Aaron Bentley
Add BranchSubscription.review_level
755
    def subscribe(self, person, notification_level, max_diff_lines,
7675.708.5 by Tim Penhey
Some other necessary bits.
756
                  code_review_level, subscribed_by):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
757
        """See `IBranch`."""
4620.2.2 by Tim Penhey
Added the misteriously disappearing doctest.
758
        # If the person is already subscribed, update the subscription with
759
        # the specified notification details.
760
        subscription = self.getSubscription(person)
761
        if subscription is None:
762
            subscription = BranchSubscription(
763
                branch=self, person=person,
764
                notification_level=notification_level,
7675.708.5 by Tim Penhey
Some other necessary bits.
765
                max_diff_lines=max_diff_lines, review_level=code_review_level,
766
                subscribed_by=subscribed_by)
5821.18.2 by James Henstridge
Add flush() calls to Branch.subscribe() and unsubscribe(), fixing
767
            Store.of(subscription).flush()
4620.2.2 by Tim Penhey
Added the misteriously disappearing doctest.
768
        else:
769
            subscription.notification_level = notification_level
770
            subscription.max_diff_lines = max_diff_lines
7781.4.8 by Paul Hummer
Fixed review_level issue
771
            subscription.review_level = code_review_level
4620.2.2 by Tim Penhey
Added the misteriously disappearing doctest.
772
        return subscription
3691.431.11 by Tim Penhey
Branch subscription pages edited
773
774
    def getSubscription(self, person):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
775
        """See `IBranch`."""
3691.431.12 by Tim Penhey
And now the tests pass again
776
        if person is None:
777
            return None
1102.1.143 by David Allouche
Review fixes to Branch view.
778
        subscription = BranchSubscription.selectOneBy(
3691.62.21 by kiko
Clean up the use of ID/.id in select*By and constructors
779
            person=person, branch=self)
3691.431.11 by Tim Penhey
Branch subscription pages edited
780
        return subscription
781
5767.1.8 by jml at canonical
Use the database instead of Python to do some querying.
782
    def getSubscriptionsByLevel(self, notification_levels):
783
        """See `IBranch`."""
7960.4.70 by Jonathan Lange
Fix up XXX comment.
784
        # XXX: JonathanLange 2009-05-07 bug=373026: This is only used by real
785
        # code to determine whether there are any subscribers at the given
786
        # notification levels. The only code that cares about the actual
787
        # object is in a test:
7960.4.23 by Jonathan Lange
Comments; shuffling transaction.commit.
788
        # test_only_nodiff_subscribers_means_no_diff_generated.
7960.4.25 by Jonathan Lange
Extract out a function that determines whether you need branch subscription
789
        store = Store.of(self)
790
        return store.find(
791
            BranchSubscription,
792
            BranchSubscription.branch == self,
793
            BranchSubscription.notification_level.is_in(notification_levels))
5767.1.8 by jml at canonical
Use the database instead of Python to do some querying.
794
3691.431.11 by Tim Penhey
Branch subscription pages edited
795
    def hasSubscription(self, person):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
796
        """See `IBranch`."""
3691.431.11 by Tim Penhey
Branch subscription pages edited
797
        return self.getSubscription(person) is not None
798
7675.708.3 by Tim Penhey
Add subscribed_by and unsubscribed_by to the subscribe and unsubscribe branch methods.
799
    def unsubscribe(self, person, unsubscribed_by):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
800
        """See `IBranch`."""
3691.431.11 by Tim Penhey
Branch subscription pages edited
801
        subscription = self.getSubscription(person)
7675.708.15 by Tim Penhey
Add the check to make sure that the user unsubscribing actually is allowed to.
802
        if subscription is None:
803
            # Silent success seems order of the day (like bugs).
804
            return
805
        if not subscription.canBeUnsubscribedByUser(unsubscribed_by):
806
            raise UserCannotUnsubscribePerson(
807
                '%s does not have permission to unsubscribe %s.' % (
808
                    unsubscribed_by.displayname,
809
                    person.displayname))
5821.18.2 by James Henstridge
Add flush() calls to Branch.subscribe() and unsubscribe(), fixing
810
        store = Store.of(subscription)
7675.708.15 by Tim Penhey
Add the check to make sure that the user unsubscribing actually is allowed to.
811
        store.remove(subscription)
5821.18.2 by James Henstridge
Add flush() calls to Branch.subscribe() and unsubscribe(), fixing
812
        store.flush()
1102.1.143 by David Allouche
Review fixes to Branch view.
813
6736.3.5 by Tim Penhey
Clean up the Branch.getBranchRevision method.
814
    def getBranchRevision(self, sequence=None, revision=None,
815
                          revision_id=None):
816
        """See `IBranch`."""
817
        params = (sequence, revision, revision_id)
6736.3.12 by Tim Penhey
Updates following review.
818
        if len([p for p in params if p is not None]) != 1:
6736.3.5 by Tim Penhey
Clean up the Branch.getBranchRevision method.
819
            raise AssertionError(
820
                "One and only one of sequence, revision, or revision_id "
821
                "should have a value.")
822
        if sequence is not None:
823
            query = BranchRevision.sequence == sequence
824
        elif revision is not None:
825
            query = BranchRevision.revision == revision
826
        else:
827
            query = And(BranchRevision.revision == Revision.id,
828
                        Revision.revision_id == revision_id)
829
830
        store = Store.of(self)
831
832
        return store.find(
833
            BranchRevision,
834
            BranchRevision.branch == self,
835
            query).one()
5280.4.3 by Tim Penhey
Workflow works nicely, resisting hard gold plating.
836
7675.814.11 by Tim Penhey
Update the branch method to take an iterable.
837
    def removeBranchRevisions(self, revision_ids):
7675.814.4 by Tim Penhey
Add implementation and remove lint.
838
        """See `IBranch`."""
7675.814.11 by Tim Penhey
Update the branch method to take an iterable.
839
        if isinstance(revision_ids, basestring):
840
            revision_ids = [revision_ids]
7675.814.4 by Tim Penhey
Add implementation and remove lint.
841
        IMasterStore(BranchRevision).find(
842
            BranchRevision,
843
            BranchRevision.branch == self,
844
            BranchRevision.revision_id.is_in(
845
                Select(Revision.id,
7675.814.11 by Tim Penhey
Update the branch method to take an iterable.
846
                       Revision.revision_id.is_in(revision_ids)))).remove()
7675.814.4 by Tim Penhey
Add implementation and remove lint.
847
3691.416.1 by David Allouche
replace RevisionNumber by BranchRevision across
848
    def createBranchRevision(self, sequence, revision):
4629.4.9 by Tim Penhey
Update following review comments
849
        """See `IBranch`."""
6623.4.1 by Tim Penhey
Initial karma allocation done. TODO: karma when revision author linked.
850
        branch_revision = BranchRevision(
3691.425.33 by David Allouche
update documentation and interfaces for BranchRevision semantics
851
            branch=self, sequence=sequence, revision=revision)
6623.4.1 by Tim Penhey
Initial karma allocation done. TODO: karma when revision author linked.
852
        # Allocate karma if no karma has been allocated for this revision.
853
        if not revision.karma_allocated:
854
            revision.allocateKarma(self)
855
        return branch_revision
3691.25.6 by James Henstridge
remove direct RevisionNumber usage in bzrsync
856
7049.1.5 by Michael Hudson
scary temporary table nonsense
857
    def createBranchRevisionFromIDs(self, revision_id_sequence_pairs):
858
        """See `IBranch`."""
7049.1.12 by Michael Hudson
test and fix for the empty-argument case
859
        if not revision_id_sequence_pairs:
860
            return
7049.1.5 by Michael Hudson
scary temporary table nonsense
861
        store = Store.of(self)
7049.1.8 by Michael Hudson
tidy
862
        store.execute(
863
            """
864
            CREATE TEMPORARY TABLE RevidSequence
865
            (revision_id text, sequence integer)
866
            """)
7049.1.5 by Michael Hudson
scary temporary table nonsense
867
        data = []
7049.1.16 by Michael Hudson
docstrings, comments
868
        for revid, sequence in revision_id_sequence_pairs:
869
            data.append('(%s, %s)' % sqlvalues(revid, sequence))
7049.1.8 by Michael Hudson
tidy
870
        data = ', '.join(data)
871
        store.execute(
872
            "INSERT INTO RevidSequence (revision_id, sequence) VALUES %s"
873
            % data)
874
        store.execute(
875
            """
876
            INSERT INTO BranchRevision (branch, revision, sequence)
877
            SELECT %s, Revision.id, RevidSequence.sequence
878
            FROM RevidSequence, Revision
879
            WHERE Revision.revision_id = RevidSequence.revision_id
880
            """ % sqlvalues(self))
881
        store.execute("DROP TABLE RevidSequence")
7049.1.5 by Michael Hudson
scary temporary table nonsense
882
3691.290.8 by Tim Penhey
tests passing for revision changes
883
    def getTipRevision(self):
4629.4.9 by Tim Penhey
Update following review comments
884
        """See `IBranch`."""
3691.290.8 by Tim Penhey
tests passing for revision changes
885
        tip_revision_id = self.last_scanned_id
886
        if tip_revision_id is None:
887
            return None
888
        return Revision.selectOneBy(revision_id=tip_revision_id)
889
7236.4.3 by Tim Penhey
Updates following review.
890
    def updateScannedDetails(self, db_revision, revision_count):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
891
        """See `IBranch`."""
7236.4.1 by Tim Penhey
Move the functionality into the branch class for update scanned details, and awaken inactive branches on new revisions.
892
        # By taking the minimum of the revision date and the date created, we
893
        # cap the revision date to make sure that we don't use a future date.
894
        # The date created is set to be the time that the revision was created
895
        # in the database, so if the revision_date is a future date, then we
896
        # use the date created instead.
7236.4.3 by Tim Penhey
Updates following review.
897
        if db_revision is None:
7236.4.1 by Tim Penhey
Move the functionality into the branch class for update scanned details, and awaken inactive branches on new revisions.
898
            revision_id = NULL_REVISION
899
            revision_date = UTC_NOW
900
        else:
7236.4.3 by Tim Penhey
Updates following review.
901
            revision_id = db_revision.revision_id
902
            revision_date = min(
903
                db_revision.revision_date, db_revision.date_created)
7236.4.1 by Tim Penhey
Move the functionality into the branch class for update scanned details, and awaken inactive branches on new revisions.
904
7236.4.3 by Tim Penhey
Updates following review.
905
        # If the branch has changed through either a different tip revision or
906
        # revision count, then update.
7236.4.1 by Tim Penhey
Move the functionality into the branch class for update scanned details, and awaken inactive branches on new revisions.
907
        if ((revision_id != self.last_scanned_id) or
908
            (revision_count != self.revision_count)):
909
            # If the date of the last revision is greated than the date last
910
            # modified, then bring the date last modified forward to the last
911
            # revision date (as long as the revision date isn't in the
912
            # future).
7236.4.3 by Tim Penhey
Updates following review.
913
            if db_revision is None or revision_date > self.date_last_modified:
7236.4.1 by Tim Penhey
Move the functionality into the branch class for update scanned details, and awaken inactive branches on new revisions.
914
                self.date_last_modified = revision_date
915
            self.last_scanned = UTC_NOW
916
            self.last_scanned_id = revision_id
917
            self.revision_count = revision_count
918
            if self.lifecycle_status in (BranchLifecycleStatus.MERGED,
919
                                         BranchLifecycleStatus.ABANDONED):
920
                self.lifecycle_status = BranchLifecycleStatus.DEVELOPMENT
3691.431.26 by Tim Penhey
added tests to confirm email format and recipients
921
4333.2.7 by Tim Penhey
Updates following review
922
    def getNotificationRecipients(self):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
923
        """See `IBranch`."""
4333.2.7 by Tim Penhey
Updates following review
924
        recipients = NotificationRecipientSet()
925
        for subscription in self.subscriptions:
4333.2.10 by Tim Penhey
yet more review updates
926
            if subscription.person.isTeam():
927
                rationale = 'Subscriber @%s' % subscription.person.name
928
            else:
929
                rationale = 'Subscriber'
930
            recipients.add(subscription.person, subscription, rationale)
4333.2.7 by Tim Penhey
Updates following review
931
        return recipients
3691.25.6 by James Henstridge
remove direct RevisionNumber usage in bzrsync
932
8128.7.1 by Jonathan Lange
Add a pending_writes property to Branch.
933
    @property
934
    def pending_writes(self):
8128.7.3 by Jonathan Lange
More documentation.
935
        """See `IBranch`.
936
8128.7.5 by Jonathan Lange
Docstring updates
937
        A branch has pending writes if it has just been pushed to, if it has
938
        been mirrored and not yet scanned or if it is in the middle of being
939
        mirrored.
8128.7.3 by Jonathan Lange
More documentation.
940
        """
8128.7.4 by Jonathan Lange
More tests.
941
        new_data_pushed = (
10984.2.1 by Tim Penhey
Raise errors if trying to mirror a hosted branch.
942
             self.branch_type == BranchType.IMPORTED
8128.7.4 by Jonathan Lange
More tests.
943
             and self.next_mirror_time is not None)
9590.1.80 by Michael Hudson
de-xxx
944
        # XXX 2010-04-22, MichaelHudson: This should really look for a branch
10984.2.15 by Tim Penhey
Comment tweak.
945
        # scan job.
8128.7.4 by Jonathan Lange
More tests.
946
        pulled_but_not_scanned = self.last_mirrored_id != self.last_scanned_id
947
        pull_in_progress = (
948
            self.last_mirror_attempt is not None
949
            and (self.last_mirrored is None
950
                 or self.last_mirror_attempt > self.last_mirrored))
8128.7.1 by Jonathan Lange
Add a pending_writes property to Branch.
951
        return (
8128.7.4 by Jonathan Lange
More tests.
952
            new_data_pushed or pulled_but_not_scanned or pull_in_progress)
8128.7.1 by Jonathan Lange
Add a pending_writes property to Branch.
953
3836.2.13 by David Allouche
refactor BranchRevisionSet.getScannerDataForBranch into Branch
954
    def getScannerData(self):
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
955
        """See `IBranch`."""
12243.2.1 by Jeroen Vermeulen
Stop using BranchRevision.id from our code.
956
        columns = (BranchRevision.sequence, Revision.revision_id)
7675.747.4 by Jeroen Vermeulen
Review changes, minus prejoins.
957
        rows = Store.of(self).using(Revision, BranchRevision).find(
958
            columns,
959
            Revision.id == BranchRevision.revision_id,
960
            BranchRevision.branch_id == self.id)
961
        rows = rows.order_by(BranchRevision.sequence)
3836.2.13 by David Allouche
refactor BranchRevisionSet.getScannerDataForBranch into Branch
962
        ancestry = set()
963
        history = []
12243.2.1 by Jeroen Vermeulen
Stop using BranchRevision.id from our code.
964
        for sequence, revision_id in rows:
3836.2.13 by David Allouche
refactor BranchRevisionSet.getScannerDataForBranch into Branch
965
            ancestry.add(revision_id)
966
            if sequence is not None:
967
                history.append(revision_id)
7675.814.8 by Tim Penhey
Simplify the branch method for getting scanner data.
968
        return ancestry, history
3836.2.13 by David Allouche
refactor BranchRevisionSet.getScannerDataForBranch into Branch
969
4756.1.14 by Jonathan Lange
Move pull URL logic into Branch
970
    def getPullURL(self):
971
        """See `IBranch`."""
972
        if self.branch_type == BranchType.MIRRORED:
973
            # This is a pull branch, hosted externally.
974
            return self.url
975
        elif self.branch_type == BranchType.IMPORTED:
976
            # This is an import branch, imported into bzr from
977
            # another RCS system such as CVS.
978
            prefix = config.launchpad.bzr_imports_root_url
979
            return urlappend(prefix, '%08x' % self.id)
980
        else:
7675.747.9 by Jeroen Vermeulen
Lint.
981
            raise AssertionError("No pull URL for %r" % (self, ))
4756.1.14 by Jonathan Lange
Move pull URL logic into Branch
982
4386.2.5 by Jonathan Lange
requestMirror uses SQLObject. Add a requestMirror method to Branch
983
    def requestMirror(self):
984
        """See `IBranch`."""
10984.2.1 by Tim Penhey
Raise errors if trying to mirror a hosted branch.
985
        if self.branch_type in (BranchType.REMOTE, BranchType.HOSTED):
4813.4.7 by Tim Penhey
Missing imports
986
            raise BranchTypeError(self.unique_name)
11134.3.3 by Jeroen Vermeulen
Drive-by lint.
987
        branch = Store.of(self).find(
9587.2.1 by Michael Hudson
probably rather over engineered fix
988
            Branch,
989
            Branch.id == self.id,
9587.2.2 by Michael Hudson
wrap long line
990
            Or(Branch.next_mirror_time > UTC_NOW,
7675.747.9 by Jeroen Vermeulen
Lint.
991
               Branch.next_mirror_time == None))
992
        branch.set(next_mirror_time=UTC_NOW)
9587.2.1 by Michael Hudson
probably rather over engineered fix
993
        self.next_mirror_time = AutoReload
5223.7.2 by jml at canonical
Rename mirror_request_time to next_mirror_time in code.
994
        return self.next_mirror_time
4386.2.5 by Jonathan Lange
requestMirror uses SQLObject. Add a requestMirror method to Branch
995
4386.2.8 by Jonathan Lange
Move startMirroring over to SQLObject, move implementation to Branch class.
996
    def startMirroring(self):
997
        """See `IBranch`."""
10984.2.1 by Tim Penhey
Raise errors if trying to mirror a hosted branch.
998
        if self.branch_type in (BranchType.REMOTE, BranchType.HOSTED):
4813.4.7 by Tim Penhey
Missing imports
999
            raise BranchTypeError(self.unique_name)
4386.2.8 by Jonathan Lange
Move startMirroring over to SQLObject, move implementation to Branch class.
1000
        self.last_mirror_attempt = UTC_NOW
6983.2.3 by Jonathan Lange
Make starting a mirror remove a branch from the pull queue.
1001
        self.next_mirror_time = None
4386.2.8 by Jonathan Lange
Move startMirroring over to SQLObject, move implementation to Branch class.
1002
12707.4.8 by Tim Penhey
Support the branch-id alias in the setting of the stacked on branch in the model code.
1003
    def _findStackedBranch(self, stacked_on_location):
1004
        location = stacked_on_location.strip('/')
1005
        if location.startswith(BRANCH_ID_ALIAS_PREFIX + '/'):
1006
            try:
1007
                branch_id = int(location.split('/', 1)[1])
1008
            except (ValueError, IndexError):
1009
                return None
1010
            return getUtility(IBranchLookup).get(branch_id)
1011
        else:
1012
            return getUtility(IBranchLookup).getByUniqueName(location)
1013
9590.4.7 by Michael Hudson
move the guts branchChanged onto the branch itself
1014
    def branchChanged(self, stacked_on_location, last_revision_id,
1015
                      control_format, branch_format, repository_format):
1016
        """See `IBranch`."""
1017
        self.mirror_status_message = None
10850.3.1 by Jeroen Vermeulen
Porting production-devel fix to devel.
1018
        if stacked_on_location == '' or stacked_on_location is None:
9590.4.7 by Michael Hudson
move the guts branchChanged onto the branch itself
1019
            stacked_on_branch = None
1020
        else:
12707.4.8 by Tim Penhey
Support the branch-id alias in the setting of the stacked on branch in the model code.
1021
            stacked_on_branch = self._findStackedBranch(stacked_on_location)
9590.4.7 by Michael Hudson
move the guts branchChanged onto the branch itself
1022
            if stacked_on_branch is None:
1023
                self.mirror_status_message = (
1024
                    'Invalid stacked on location: ' + stacked_on_location)
1025
        self.stacked_on = stacked_on_branch
9590.1.73 by Michael Hudson
tweaks to get the puller acceptance tests running
1026
        if self.branch_type == BranchType.HOSTED:
1027
            self.last_mirrored = UTC_NOW
1028
        else:
1029
            self.last_mirrored = self.last_mirror_attempt
9590.4.7 by Michael Hudson
move the guts branchChanged onto the branch itself
1030
        self.mirror_failures = 0
1031
        if (self.next_mirror_time is None
1032
            and self.branch_type == BranchType.MIRRORED):
1033
            # No mirror was requested since we started mirroring.
1034
            increment = getUtility(IBranchPuller).MIRROR_TIME_INCREMENT
1035
            self.next_mirror_time = (
9590.1.73 by Michael Hudson
tweaks to get the puller acceptance tests running
1036
                datetime.now(pytz.timezone('UTC')) + increment)
11573.6.2 by Tim Penhey
Only create a scan job if the new revision is different to the last scanned revision.
1037
        self.last_mirrored_id = last_revision_id
1038
        if self.last_scanned_id != last_revision_id:
9590.4.7 by Michael Hudson
move the guts branchChanged onto the branch itself
1039
            from lp.code.model.branchjob import BranchScanJob
1040
            BranchScanJob.create(self)
1041
        self.control_format = control_format
1042
        self.branch_format = branch_format
1043
        self.repository_format = repository_format
1044
4386.2.10 by Jonathan Lange
Move mirrorFailed's implementation to SQLObject, change RPC method to
1045
    def mirrorFailed(self, reason):
1046
        """See `IBranch`."""
10984.2.1 by Tim Penhey
Raise errors if trying to mirror a hosted branch.
1047
        if self.branch_type in (BranchType.REMOTE, BranchType.HOSTED):
4813.4.7 by Tim Penhey
Missing imports
1048
            raise BranchTypeError(self.unique_name)
4386.2.10 by Jonathan Lange
Move mirrorFailed's implementation to SQLObject, change RPC method to
1049
        self.mirror_failures += 1
1050
        self.mirror_status_message = reason
8028.3.7 by Jonathan Lange
Store it on a local variable.
1051
        branch_puller = getUtility(IBranchPuller)
1052
        max_failures = branch_puller.MAXIMUM_MIRROR_FAILURES
1053
        increment = branch_puller.MIRROR_TIME_INCREMENT
4795.1.6 by Jonathan Lange
Don't backoff imported or hosted branches -- they aren't going to correct
1054
        if (self.branch_type == BranchType.MIRRORED
8028.3.2 by Jonathan Lange
Move the puller interface off branchset and into a new utility interface
1055
            and self.mirror_failures < max_failures):
5223.7.2 by jml at canonical
Rename mirror_request_time to next_mirror_time in code.
1056
            self.next_mirror_time = (
4795.1.4 by Jonathan Lange
Stop mirroring after a certain number of failures.
1057
                datetime.now(pytz.timezone('UTC'))
8028.3.2 by Jonathan Lange
Move the puller interface off branchset and into a new utility interface
1058
                + increment * 2 ** (self.mirror_failures - 1))
4386.2.10 by Jonathan Lange
Move mirrorFailed's implementation to SQLObject, change RPC method to
1059
10054.25.9 by Jelmer Vernooij
Drop the underscore.
1060
    def destroySelfBreakReferences(self):
10054.25.7 by Jelmer Vernooij
Break references by default in the Branch destructor, per Thumpers suggestion.
1061
        """See `IBranch`."""
12252.1.5 by j.c.sackett
Test running.
1062
        try:
1063
            return self.destroySelf(break_references=True)
1064
        except CannotDeleteBranch, e:
1065
            # Reraise and expose exception here so that the webservice_error
1066
            # is propogated.
12499.1.5 by Leonard Richardson
Fix bug 728507 by removing calls to export().
1067
            raise CannotDeleteBranch(e.message)
10054.25.7 by Jelmer Vernooij
Break references by default in the Branch destructor, per Thumpers suggestion.
1068
11134.3.2 by Jeroen Vermeulen
Clean up BuildQueues for BranchJobs.
1069
    def _deleteBranchSubscriptions(self):
1070
        """Delete subscriptions for this branch prior to deleting branch."""
1071
        subscriptions = Store.of(self).find(
1072
            BranchSubscription, BranchSubscription.branch == self)
1073
        subscriptions.remove()
1074
1075
    def _deleteJobs(self):
1076
        """Delete jobs for this branch prior to deleting branch.
1077
1078
        This deletion includes `BranchJob`s associated with the branch,
7675.823.2 by Jeroen Vermeulen
Register extra branch deletion requirement.
1079
        as well as `BuildQueue` entries for `TranslationTemplateBuildJob`s
1080
        and `TranslationTemplateBuild`s.
11134.3.2 by Jeroen Vermeulen
Clean up BuildQueues for BranchJobs.
1081
        """
1082
        # Avoid circular imports.
1083
        from lp.code.model.branchjob import BranchJob
7675.823.3 by Jeroen Vermeulen
Circular import.
1084
        from lp.translations.model.translationtemplatesbuild import (
1085
            TranslationTemplatesBuild,
1086
            )
11134.3.2 by Jeroen Vermeulen
Clean up BuildQueues for BranchJobs.
1087
1088
        store = Store.of(self)
1089
        affected_jobs = Select(
1090
            [BranchJob.jobID],
1091
            And(BranchJob.job == Job.id, BranchJob.branch == self))
1092
1093
        # Delete BuildQueue entries for affected Jobs.  They would pin
1094
        # the affected Jobs in the database otherwise.
1095
        store.find(BuildQueue, BuildQueue.jobID.is_in(affected_jobs)).remove()
1096
1097
        # Delete Jobs.  Their BranchJobs cascade along in the database.
1098
        store.find(Job, Job.id.is_in(affected_jobs)).remove()
1099
7675.823.2 by Jeroen Vermeulen
Register extra branch deletion requirement.
1100
        store.find(
1101
            TranslationTemplatesBuild,
1102
            TranslationTemplatesBuild.branch == self).remove()
1103
5616.2.16 by Aaron Bentley
Change BranchSet.delete to Branch.destroySelf
1104
    def destroySelf(self, break_references=False):
1105
        """See `IBranch`."""
7675.202.11 by Michael Hudson
create a reclaimbranchspacejob on branch deletion
1106
        from lp.code.interfaces.branchjob import IReclaimBranchSpaceJobSource
5616.2.16 by Aaron Bentley
Change BranchSet.delete to Branch.destroySelf
1107
        if break_references:
1108
            self._breakReferences()
8211.4.11 by Jonathan Lange
Shuffle around slightly to reduce indentation.
1109
        if not self.canBeDeleted():
5616.2.16 by Aaron Bentley
Change BranchSet.delete to Branch.destroySelf
1110
            raise CannotDeleteBranch(
1111
                "Cannot delete branch: %s" % self.unique_name)
11134.3.2 by Jeroen Vermeulen
Clean up BuildQueues for BranchJobs.
1112
1113
        self._deleteBranchSubscriptions()
1114
        self._deleteJobs()
1115
8211.4.11 by Jonathan Lange
Shuffle around slightly to reduce indentation.
1116
        # Now destroy the branch.
7675.202.11 by Michael Hudson
create a reclaimbranchspacejob on branch deletion
1117
        branch_id = self.id
8211.4.11 by Jonathan Lange
Shuffle around slightly to reduce indentation.
1118
        SQLBase.destroySelf(self)
7675.202.11 by Michael Hudson
create a reclaimbranchspacejob on branch deletion
1119
        # And now create a job to remove the branch from disk when it's done.
1120
        getUtility(IReclaimBranchSpaceJobSource).create(branch_id)
5616.2.16 by Aaron Bentley
Change BranchSet.delete to Branch.destroySelf
1121
8137.17.24 by Barry Warsaw
thread merge
1122
    def commitsForDays(self, since):
1123
        """See `IBranch`."""
7675.747.9 by Jeroen Vermeulen
Lint.
1124
8137.17.24 by Barry Warsaw
thread merge
1125
        class DateTrunc(NamedFunc):
1126
            name = "date_trunc"
11134.3.3 by Jeroen Vermeulen
Drive-by lint.
1127
8137.17.24 by Barry Warsaw
thread merge
1128
        results = Store.of(self).find(
7675.166.305 by Stuart Bishop
DB Unicode fixes
1129
            (DateTrunc(u'day', Revision.revision_date), Count(Revision.id)),
7675.747.1 by Jeroen Vermeulen
Intermediate state: stormifying BranchRevision.
1130
            Revision.id == BranchRevision.revision_id,
8137.17.24 by Barry Warsaw
thread merge
1131
            Revision.revision_date > since,
1132
            BranchRevision.branch == self)
1133
        results = results.group_by(
7675.166.305 by Stuart Bishop
DB Unicode fixes
1134
            DateTrunc(u'day', Revision.revision_date))
8137.17.24 by Barry Warsaw
thread merge
1135
        return sorted(results)
1136
8303.17.1 by Paul Hummer
Added tests for needsUpgrading
1137
    @property
8303.17.5 by Paul Hummer
Responded to Aaron's second review
1138
    def needs_upgrading(self):
8303.17.1 by Paul Hummer
Added tests for needsUpgrading
1139
        """See `IBranch`."""
10249.1.2 by Paul Hummer
Added logic and tests to ensure that the branch is a hosted branch, so we don't try and upgrade other branch types.
1140
        if self.branch_type is not BranchType.HOSTED:
1141
            return False
7675.477.1 by Paul Hummer
Reverted the reversion of the patch that went into devel by mistake
1142
        if self.upgrade_pending:
1143
            return False
1144
        return not (
1145
            self.branch_format in CURRENT_BRANCH_FORMATS and
1146
            self.repository_format in CURRENT_REPOSITORY_FORMATS)
1147
1148
    @property
1149
    def upgrade_pending(self):
1150
        """See `IBranch`."""
1151
        from lp.code.model.branchjob import BranchJob, BranchJobType
1152
        store = Store.of(self)
1153
        jobs = store.find(
1154
            BranchJob,
1155
            BranchJob.branch == self,
7675.493.2 by Paul Hummer
Fixed an issue with upgrade_pending
1156
            Job.id == BranchJob.jobID,
1157
            Job._status != JobStatus.COMPLETED,
1158
            Job._status != JobStatus.FAILED,
7675.477.1 by Paul Hummer
Reverted the reversion of the patch that went into devel by mistake
1159
            BranchJob.job_type == BranchJobType.UPGRADE_BRANCH)
1160
        return jobs.count() > 0
10174.2.4 by Paul Hummer
Added IBranch.upgrade_pending property.
1161
9644.8.6 by Paul Hummer
Added test and stub code for IBranch.requestUpgrade
1162
    def requestUpgrade(self):
1163
        """See `IBranch`."""
9644.8.9 by Paul Hummer
Changed how the job gets created
1164
        from lp.code.interfaces.branchjob import IBranchUpgradeJobSource
1165
        return getUtility(IBranchUpgradeJobSource).create(self)
9644.8.6 by Paul Hummer
Added test and stub code for IBranch.requestUpgrade
1166
9550.16.2 by Tim Penhey
Move the access branch logic into the branch itself.
1167
    def _checkBranchVisibleByUser(self, user):
1168
        """Is *this* branch visible by the user.
1169
1170
        This method doesn't check the stacked upon branch.  That is handled by
1171
        the `visibleByUser` method.
1172
        """
1173
        if not self.private:
1174
            return True
1175
        if user is None:
1176
            return False
1177
        if user.inTeam(self.owner):
1178
            return True
1179
        for subscriber in self.subscribers:
1180
            if user.inTeam(subscriber):
1181
                return True
1182
        return user_has_special_branch_access(user)
1183
1184
    def visibleByUser(self, user, checked_branches=None):
1185
        """See `IBranch`."""
1186
        if checked_branches is None:
1187
            checked_branches = []
1188
        can_access = self._checkBranchVisibleByUser(user)
1189
        if can_access and self.stacked_on is not None:
1190
            checked_branches.append(self)
1191
            if self.stacked_on not in checked_branches:
1192
                can_access = self.stacked_on.visibleByUser(
1193
                    user, checked_branches)
1194
        return can_access
1195
12397.2.8 by Ian Booth
Change from using getter methods to properties for exported recipe and build accessors
1196
    @property
1197
    def recipes(self):
7675.615.4 by Paul Hummer
Added IHasRecipes
1198
        """See `IHasRecipes`."""
7675.615.9 by Paul Hummer
SUCCESS!!!!
1199
        from lp.code.model.sourcepackagerecipedata import (
1200
            SourcePackageRecipeData)
10498.5.36 by Aaron Bentley
Deleting branches used in recipes deletes the recipes.
1201
        return SourcePackageRecipeData.findRecipes(self)
7675.615.4 by Paul Hummer
Added IHasRecipes
1202
7675.887.20 by Paul Hummer
Added IBranch.addToQueue with accompanying tests
1203
    merge_queue_id = Int(name='merge_queue', allow_none=True)
1204
    merge_queue = Reference(merge_queue_id, 'BranchMergeQueue.id')
1205
7675.887.18 by Paul Hummer
Added merge_queue and merge_queue_config, removed getMergeQueue
1206
    merge_queue_config = StringCol(dbName='merge_queue_config')
1207
7675.887.20 by Paul Hummer
Added IBranch.addToQueue with accompanying tests
1208
    def addToQueue(self, queue):
1209
        """See `IBranchEdit`."""
1210
        self.merge_queue = queue
1211
7675.887.21 by Paul Hummer
Added IBranch.setMergeQueueConfig with accompanying tests
1212
    def setMergeQueueConfig(self, config):
1213
        """See `IBranchEdit`."""
1214
        try:
1215
            simplejson.loads(config)
1216
            self.merge_queue_config = config
1217
        except ValueError: # The json string is invalid
1218
            raise InvalidMergeQueueConfig
1219
4960.1.13 by Michael Hudson
docstrings and line lengths in database/branch.py
1220
6019.2.1 by Aaron Bentley
Use objects instead of closures for deletion
1221
class DeletionOperation:
1222
    """Represent an operation to perform as part of branch deletion."""
1223
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
1224
    def __init__(self, affected_object, rationale):
10498.5.42 by Aaron Bentley
Ensure branch deletion respects zope permissions
1225
        self.affected_object = ProxyFactory(affected_object)
6019.2.4 by Aaron Bentley
Move affected_object, rationale into into DeletionOperation
1226
        self.rationale = rationale
7675.747.9 by Jeroen Vermeulen
Lint.
1227
6019.2.10 by Aaron Bentley
Updatest from review
1228
    def __call__(self):
1229
        """Perform the deletion operation."""
1230
        raise NotImplementedError(DeletionOperation.__call__)
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
1231
6019.2.11 by Aaron Bentley
PEP8
1232
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
1233
class DeletionCallable(DeletionOperation):
6019.2.7 by Aaron Bentley
Update docs
1234
    """Deletion operation that invokes a callable."""
1235
6019.2.8 by Aaron Bentley
Remove DeletionCallable.func_args, add tests
1236
    def __init__(self, affected_object, rationale, func):
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
1237
        DeletionOperation.__init__(self, affected_object, rationale)
6019.2.1 by Aaron Bentley
Use objects instead of closures for deletion
1238
        self.func = func
1239
6019.2.10 by Aaron Bentley
Updatest from review
1240
    def __call__(self):
6019.2.8 by Aaron Bentley
Remove DeletionCallable.func_args, add tests
1241
        self.func()
6019.2.1 by Aaron Bentley
Use objects instead of closures for deletion
1242
10498.5.36 by Aaron Bentley
Deleting branches used in recipes deletes the recipes.
1243
    @classmethod
1244
    def forSourcePackageRecipe(cls, recipe):
1245
        return cls(
1246
            recipe, _('This recipe uses this branch.'), recipe.destroySelf)
1247
6019.2.1 by Aaron Bentley
Use objects instead of closures for deletion
1248
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
1249
class ClearDependentBranch(DeletionOperation):
7675.343.1 by Aaron Bentley
Rename dependent branch to prerequisite branch.
1250
    """Delete operation that clears a merge proposal's prerequisite branch."""
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
1251
1252
    def __init__(self, merge_proposal):
1253
        DeletionOperation.__init__(self, merge_proposal,
7675.343.1 by Aaron Bentley
Rename dependent branch to prerequisite branch.
1254
            _('This branch is the prerequisite branch of this merge'
1255
              ' proposal.'))
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
1256
6019.2.10 by Aaron Bentley
Updatest from review
1257
    def __call__(self):
7675.343.1 by Aaron Bentley
Rename dependent branch to prerequisite branch.
1258
        self.affected_object.prerequisite_branch = None
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
1259
1260
1261
class ClearSeriesBranch(DeletionOperation):
6019.2.7 by Aaron Bentley
Update docs
1262
    """Deletion operation that clears a series' branch."""
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
1263
1264
    def __init__(self, series, branch):
1265
        DeletionOperation.__init__(
1266
            self, series, _('This series is linked to this branch.'))
1267
        self.branch = branch
1268
6019.2.10 by Aaron Bentley
Updatest from review
1269
    def __call__(self):
7675.85.2 by Jonathan Lange
Undo revision generated by step 2 of process.
1270
        if self.affected_object.branch == self.branch:
1271
            self.affected_object.branch = None
6019.2.6 by Aaron Bentley
Split callables out, convert to subclasses
1272
1273
8771.6.1 by Jeroen Vermeulen
UI for exporting translations to bzr branch.
1274
class ClearSeriesTranslationsBranch(DeletionOperation):
1275
    """Deletion operation that clears a series' translations branch."""
1276
1277
    def __init__(self, series, branch):
1278
        DeletionOperation.__init__(
1279
            self, series,
1280
            _('This series exports its translations to this branch.'))
1281
        self.branch = branch
1282
1283
    def __call__(self):
11316.6.2 by Tim Penhey
The translation clearing class didn't do what it said, and it wasn't tested.
1284
        if self.affected_object.translations_branch == self.branch:
1285
            self.affected_object.translations_branch = None
8771.6.1 by Jeroen Vermeulen
UI for exporting translations to bzr branch.
1286
1287
8211.4.12 by Jonathan Lange
Allow branches linked to source packages to be deleted.
1288
class ClearOfficialPackageBranch(DeletionOperation):
1289
    """Deletion operation that clears an official package branch."""
1290
1291
    def __init__(self, sspb):
1292
        DeletionOperation.__init__(
1293
            self, sspb, _('Branch is officially linked to a source package.'))
1294
1295
    def __call__(self):
1296
        package = self.affected_object.sourcepackage
1297
        pocket = self.affected_object.pocket
1298
        package.setBranch(pocket, None, None)
1299
1300
6019.2.9 by Aaron Bentley
Add DeleteCodeImport class
1301
class DeleteCodeImport(DeletionOperation):
1302
    """Deletion operation that deletes a branch's import."""
1303
1304
    def __init__(self, code_import):
1305
        DeletionOperation.__init__(
7675.747.9 by Jeroen Vermeulen
Lint.
1306
            self, code_import, _('This is the import data for this branch.'))
6019.2.9 by Aaron Bentley
Add DeleteCodeImport class
1307
6019.2.10 by Aaron Bentley
Updatest from review
1308
    def __call__(self):
8138.1.2 by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing.
1309
        from lp.code.model.codeimport import CodeImportSet
6019.2.9 by Aaron Bentley
Add DeleteCodeImport class
1310
        CodeImportSet().delete(self.affected_object)
1311
1312
1102.1.80 by David Allouche
product/+addbranch, branch.registrant to branch.author in db patch and sample data
1313
class BranchSet:
1314
    """The set of all branches."""
1315
8028.2.5 by Jonathan Lange
Move the implementation.
1316
    implements(IBranchSet)
1102.1.80 by David Allouche
product/+addbranch, branch.registrant to branch.author in db patch and sample data
1317
4619.1.1 by Tim Penhey
Project cloud preview, and more... for code front page listings.
1318
    def getRecentlyChangedBranches(
1319
        self, branch_count=None,
1320
        lifecycle_statuses=DEFAULT_BRANCH_STATUS_IN_LISTING,
1321
        visible_by_user=None):
1322
        """See `IBranchSet`."""
7839.2.1 by Jonathan Lange
Make all of the BranchSet.getRecentFoo methods use IBranchCollection.
1323
        all_branches = getUtility(IAllBranches)
1324
        branches = all_branches.visibleByUser(
1325
            visible_by_user).withLifecycleStatus(*lifecycle_statuses)
1326
        branches = branches.withBranchType(
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
1327
            BranchType.HOSTED, BranchType.MIRRORED).scanned().getBranches(
1328
                eager_load=False)
7839.2.1 by Jonathan Lange
Make all of the BranchSet.getRecentFoo methods use IBranchCollection.
1329
        branches.order_by(
7675.92.2 by Tim Penhey
Make the pagetests work again.
1330
            Desc(Branch.date_last_modified), Desc(Branch.id))
4619.1.1 by Tim Penhey
Project cloud preview, and more... for code front page listings.
1331
        if branch_count is not None:
7839.2.1 by Jonathan Lange
Make all of the BranchSet.getRecentFoo methods use IBranchCollection.
1332
            branches.config(limit=branch_count)
1333
        return branches
4619.1.1 by Tim Penhey
Project cloud preview, and more... for code front page listings.
1334
1335
    def getRecentlyImportedBranches(
1336
        self, branch_count=None,
1337
        lifecycle_statuses=DEFAULT_BRANCH_STATUS_IN_LISTING,
1338
        visible_by_user=None):
1339
        """See `IBranchSet`."""
7839.2.1 by Jonathan Lange
Make all of the BranchSet.getRecentFoo methods use IBranchCollection.
1340
        all_branches = getUtility(IAllBranches)
1341
        branches = all_branches.visibleByUser(
1342
            visible_by_user).withLifecycleStatus(*lifecycle_statuses)
1343
        branches = branches.withBranchType(
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
1344
            BranchType.IMPORTED).scanned().getBranches(eager_load=False)
7839.2.1 by Jonathan Lange
Make all of the BranchSet.getRecentFoo methods use IBranchCollection.
1345
        branches.order_by(
7675.92.2 by Tim Penhey
Make the pagetests work again.
1346
            Desc(Branch.date_last_modified), Desc(Branch.id))
4619.1.1 by Tim Penhey
Project cloud preview, and more... for code front page listings.
1347
        if branch_count is not None:
7839.2.1 by Jonathan Lange
Make all of the BranchSet.getRecentFoo methods use IBranchCollection.
1348
            branches.config(limit=branch_count)
1349
        return branches
4619.1.1 by Tim Penhey
Project cloud preview, and more... for code front page listings.
1350
1351
    def getRecentlyRegisteredBranches(
1352
        self, branch_count=None,
1353
        lifecycle_statuses=DEFAULT_BRANCH_STATUS_IN_LISTING,
1354
        visible_by_user=None):
1355
        """See `IBranchSet`."""
7839.2.1 by Jonathan Lange
Make all of the BranchSet.getRecentFoo methods use IBranchCollection.
1356
        all_branches = getUtility(IAllBranches)
1357
        branches = all_branches.withLifecycleStatus(
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
1358
            *lifecycle_statuses).visibleByUser(visible_by_user).getBranches(
1359
                eager_load=False)
7839.2.1 by Jonathan Lange
Make all of the BranchSet.getRecentFoo methods use IBranchCollection.
1360
        branches.order_by(
1361
            Desc(Branch.date_created), Desc(Branch.id))
4619.1.1 by Tim Penhey
Project cloud preview, and more... for code front page listings.
1362
        if branch_count is not None:
7839.2.1 by Jonathan Lange
Make all of the BranchSet.getRecentFoo methods use IBranchCollection.
1363
            branches.config(limit=branch_count)
1364
        return branches
3754.2.3 by Tim Penhey
added IBranchSet methods to populate code.launchpad.net page
1365
8777.4.6 by Jonathan Lange
Merge the new branches interface with branch set.
1366
    def getByUniqueName(self, unique_name):
1367
        """See `IBranchSet`."""
1368
        return getUtility(IBranchLookup).getByUniqueName(unique_name)
1369
1370
    def getByUrl(self, url):
1371
        """See `IBranchSet`."""
1372
        return getUtility(IBranchLookup).getByUrl(url)
1373
9841.2.2 by Jonathan Lange
Actually expose the method over the API, but it doesn't work because we
1374
    def getByUrls(self, urls):
1375
        """See `IBranchSet`."""
9841.2.11 by Jonathan Lange
Re-add the code to branch lookup, since it's actually kind of useful.
1376
        return getUtility(IBranchLookup).getByUrls(urls)
9841.2.2 by Jonathan Lange
Actually expose the method over the API, but it doesn't work because we
1377
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
1378
    def getBranches(self, limit=50, eager_load=True):
8777.4.6 by Jonathan Lange
Merge the new branches interface with branch set.
1379
        """See `IBranchSet`."""
1380
        anon_branches = getUtility(IAllBranches).visibleByUser(None)
12504.1.3 by Robert Collins
Reject reversion of this branch on trunk.
1381
        branches = anon_branches.scanned().getBranches(eager_load=eager_load)
8777.4.6 by Jonathan Lange
Merge the new branches interface with branch set.
1382
        branches.order_by(
1383
            Desc(Branch.date_last_modified), Desc(Branch.id))
1384
        branches.config(limit=limit)
1385
        return branches
1386
6266.2.1 by Tim Penhey
Refactor the many getBranchesForXXX into a single getBranchesForContext.
1387
7675.171.10 by Tim Penhey
Have a single subscriber to the modified event do the field updating and call the method to send the branch email.
1388
def update_trigger_modified_fields(branch):
1389
    """Make the trigger updated fields reload when next accessed."""
7675.171.6 by Tim Penhey
Add a function to reload the trigger update fields on an object modifed event.
1390
    # Not all the fields are exposed through the interface, and some are read
1391
    # only, so remove the security proxy.
1392
    naked_branch = removeSecurityProxy(branch)
1393
    naked_branch.unique_name = AutoReload
1394
    naked_branch.owner_name = AutoReload
1395
    naked_branch.target_suffix = AutoReload
7675.171.10 by Tim Penhey
Have a single subscriber to the modified event do the field updating and call the method to send the branch email.
1396
1397
1398
def branch_modified_subscriber(branch, event):
1399
    """This method is subscribed to IObjectModifiedEvents for branches.
1400
1401
    We have a single subscriber registered and dispatch from here to ensure
1402
    that the database fields are updated first before other subscribers.
1403
    """
1404
    update_trigger_modified_fields(branch)
1405
    send_branch_modified_notifications(branch, event)