29
26
from storm.info import ClassAlias
30
from storm.store import EmptyResultSet
31
27
from zope.component import getUtility
32
28
from zope.interface import implements
34
from canonical.database.sqlbase import quote
35
from lp.services.database.lpstorm import IStore
36
from canonical.launchpad.searchbuilder import any
30
from canonical.launchpad.components.decoratedresultset import (
33
from canonical.launchpad.interfaces.lpstorm import IStore
37
34
from canonical.launchpad.webapp.interfaces import (
39
from canonical.launchpad.searchbuilder import any
42
40
from canonical.launchpad.webapp.vocabulary import CountableIterator
41
from canonical.lazr.utils import safe_hasattr
43
42
from lp.bugs.interfaces.bugtask import (
44
44
BugTaskSearchParams,
47
46
from lp.bugs.interfaces.bugtaskfilter import filter_bugtasks_by_context
48
47
from lp.bugs.model.bugbranch import BugBranch
49
48
from lp.bugs.model.bugtask import BugTask
50
from lp.code.enums import BranchMergeProposalStatus
51
49
from lp.code.interfaces.branch import user_has_special_branch_access
52
50
from lp.code.interfaces.branchcollection import (
54
from lp.code.interfaces.seriessourcepackagebranch import (
55
IFindOfficialBranchLinks,
57
from lp.code.enums import BranchMergeProposalStatus
56
58
from lp.code.interfaces.branchlookup import IBranchLookup
57
59
from lp.code.interfaces.codehosting import LAUNCHPAD_SERVICES
58
from lp.code.interfaces.seriessourcepackagebranch import (
59
IFindOfficialBranchLinks,
61
60
from lp.code.model.branch import Branch
62
61
from lp.code.model.branchmergeproposal import BranchMergeProposal
63
62
from lp.code.model.branchsubscription import BranchSubscription
64
63
from lp.code.model.codeimport import CodeImport
65
64
from lp.code.model.codereviewcomment import CodeReviewComment
66
65
from lp.code.model.codereviewvote import CodeReviewVoteReference
66
from lp.code.model.diff import (
67
70
from lp.code.model.seriessourcepackagebranch import SeriesSourcePackageBranch
68
71
from lp.registry.model.distribution import Distribution
69
72
from lp.registry.model.distroseries import DistroSeries
70
73
from lp.registry.model.person import (
74
78
from lp.registry.model.product import Product
75
79
from lp.registry.model.sourcepackagename import SourcePackageName
123
126
if exclude_from_search is None:
124
127
exclude_from_search = []
125
128
self._exclude_from_search = exclude_from_search
129
131
"""See `IBranchCollection`."""
130
132
return self.getBranches(eager_load=False).count()
133
"""See `IBranchCollection`."""
134
return self.getBranches(eager_load=False).is_empty()
136
134
def ownerCounts(self):
137
135
"""See `IBranchCollection`."""
138
136
is_team = Person.teamowner != None
220
218
With("candidate_branches", SQL("SELECT id from scope_branches"))]
223
def preloadVisibleStackedOnBranches(branches, user=None):
224
"""Preload the chains of stacked on branches related to the given list
225
of branches. Only the branches visible for the given user are
229
if len(branches) == 0:
231
store = IStore(Branch)
232
result = store.execute("""
233
WITH RECURSIVE stacked_on_branches_ids AS (
234
SELECT column1 as id FROM (VALUES %s) AS temp
236
SELECT DISTINCT branch.stacked_on
237
FROM stacked_on_branches_ids, Branch AS branch
239
branch.id = stacked_on_branches_ids.id AND
240
branch.stacked_on IS NOT NULL
242
SELECT id from stacked_on_branches_ids
245
for id in map(attrgetter('id'), branches)]))
246
branch_ids = [res[0] for res in result.get_all()]
247
# Not really sure this is useful: if a given branch is visible by a
248
# user, then I think it means that the whole chain of branches on
249
# which is is stacked on is visible by this user
250
expressions = [Branch.id.is_in(branch_ids)]
252
collection = AnonymousBranchCollection(
253
branch_filter_expressions=expressions)
255
collection = VisibleBranchCollection(
256
user=user, branch_filter_expressions=expressions)
257
return list(collection.getBranches())
260
def preloadDataForBranches(branches):
220
def _preloadDataForBranches(self, branches):
261
221
"""Preload branches cached associated product series and
262
222
suite source packages."""
263
223
caches = dict((branch.id, get_property_cache(branch))
320
280
target_branch=None, merged_revnos=None,
321
281
eager_load=False):
322
282
"""See `IBranchCollection`."""
323
if for_branches is not None and not for_branches:
324
# We have an empty branches list, so we can shortcut.
325
return EmptyResultSet()
326
elif merged_revnos is not None and not merged_revnos:
327
# We have an empty revnos list, so we can shortcut.
328
return EmptyResultSet()
329
elif (self._asymmetric_filter_expressions or
330
for_branches is not None or
331
target_branch is not None or
332
merged_revnos is not None):
333
return self._naiveGetMergeProposals(
334
statuses, for_branches, target_branch, merged_revnos,
335
eager_load=eager_load)
283
if (self._asymmetric_filter_expressions or for_branches or
284
target_branch or merged_revnos):
285
return self._naiveGetMergeProposals(statuses, for_branches,
286
target_branch, merged_revnos, eager_load)
337
288
# When examining merge proposals in a scope, this is a moderately
338
289
# effective set of constrained queries. It is not effective when
339
290
# unscoped or when tight constraints on branches are present.
340
return self._scopedGetMergeProposals(
341
statuses, eager_load=eager_load)
291
return self._scopedGetMergeProposals(statuses)
343
293
def _naiveGetMergeProposals(self, statuses=None, for_branches=None,
344
target_branch=None, merged_revnos=None,
294
target_branch=None, merged_revnos=None, eager_load=False):
296
def do_eager_load(rows):
301
branch_ids.add(mp.target_branchID)
302
branch_ids.add(mp.source_branchID)
303
person_ids.add(mp.registrantID)
304
person_ids.add(mp.merge_reporterID)
305
diff_ids.add(mp.preview_diff_id)
309
# Pre-load Person and ValidPersonCache.
310
list(self.store.find(
311
(Person, ValidPersonCache),
312
ValidPersonCache.id == Person.id,
313
Person.id.is_in(person_ids),
316
# Pre-load PreviewDiffs and Diffs.
317
list(self.store.find(
319
PreviewDiff.id.is_in(diff_ids),
320
Diff.id == PreviewDiff.diff_id))
323
self.store.find(Branch, Branch.id.is_in(branch_ids)))
324
self._preloadDataForBranches(branches)
346
326
Target = ClassAlias(Branch, "target")
347
327
extra_tables = list(set(
348
328
self._tables.values() + self._asymmetric_tables.values()))
373
353
if not eager_load:
377
BranchMergeProposal.preloadDataForBMPs, user=self._user)
378
return DecoratedResultSet(resultset, pre_iter_hook=loader)
356
return DecoratedResultSet(resultset, pre_iter_hook=do_eager_load)
380
def _scopedGetMergeProposals(self, statuses, eager_load=False):
358
def _scopedGetMergeProposals(self, statuses):
381
359
scope_tables = [Branch] + self._tables.values()
382
360
scope_expressions = self._branch_filter_expressions
383
361
select = self.store.using(*scope_tables).find(
384
(Branch.id, Branch.transitively_private, Branch.ownerID),
362
(Branch.id, Branch.private, Branch.ownerID), *scope_expressions)
386
363
branches_query = select._get_select()
387
364
with_expr = [With("scope_branches", branches_query)
388
365
] + self._getCandidateBranchesWith()
400
377
if statuses is not None:
401
378
expressions.append(
402
379
BranchMergeProposal.queue_status.is_in(statuses))
403
resultset = self.store.with_(with_expr).using(*tables).find(
380
return self.store.with_(with_expr).using(*tables).find(
404
381
BranchMergeProposal, *expressions)
409
BranchMergeProposal.preloadDataForBMPs, user=self._user)
410
return DecoratedResultSet(resultset, pre_iter_hook=loader)
412
def getMergeProposalsForPerson(self, person, status=None,
383
def getMergeProposalsForPerson(self, person, status=None):
414
384
"""See `IBranchCollection`."""
415
385
# We want to limit the proposals to those where the source branch is
416
386
# limited by the defined collection.
417
387
owned = self.ownedBy(person).getMergeProposals(status)
418
388
reviewing = self.getMergeProposalsForReviewer(person, status)
419
resultset = owned.union(reviewing)
425
BranchMergeProposal.preloadDataForBMPs, user=self._user)
426
return DecoratedResultSet(resultset, pre_iter_hook=loader)
389
return owned.union(reviewing)
428
391
def getMergeProposalsForReviewer(self, reviewer, status=None):
429
392
"""See `IBranchCollection`."""
876
838
# Really an anonymous sitation
878
840
With("candidate_branches",
880
select id from scope_branches
881
where not transitively_private"""))
841
SQL("select id from scope_branches where not private"))
884
844
With("teams", self.store.find(TeamParticipation.teamID,
885
845
TeamParticipation.personID == person.id)._get_select()),
886
846
With("private_branches", SQL("""
887
847
SELECT scope_branches.id FROM scope_branches WHERE
888
scope_branches.transitively_private AND (
848
scope_branches.private AND (
889
849
(scope_branches.owner in (select team from teams) OR
890
850
EXISTS(SELECT true from BranchSubscription, teams WHERE
891
851
branchsubscription.branch = scope_branches.id AND
892
852
branchsubscription.person = teams.team)))""")),
893
853
With("candidate_branches", SQL("""
894
854
(SELECT id FROM private_branches) UNION
895
(select id FROM scope_branches
896
WHERE not transitively_private)"""))
855
(select id FROM scope_branches WHERE not private)"""))
899
858
def visibleByUser(self, person):