~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/code/model/branchcollection.py

  • Committer: Curtis Hovey
  • Date: 2011-08-21 14:21:06 UTC
  • mto: This revision was merged to the branch mainline in revision 13745.
  • Revision ID: curtis.hovey@canonical.com-20110821142106-x93hajd6iguma8gx
Update test that was enforcing bad grammar.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
 
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
3
 
4
4
"""Implementations of `IBranchCollection`."""
9
9
    ]
10
10
 
11
11
from collections import defaultdict
12
 
from functools import partial
13
 
from operator import attrgetter
14
12
 
15
 
from lazr.restful.utils import safe_hasattr
16
13
from storm.expr import (
17
14
    And,
18
15
    Count,
27
24
    With,
28
25
    )
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
33
29
 
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 (
 
31
    DecoratedResultSet,
 
32
    )
 
33
from canonical.launchpad.interfaces.lpstorm import IStore
37
34
from canonical.launchpad.webapp.interfaces import (
38
35
    DEFAULT_FLAVOR,
39
36
    IStoreSelector,
40
37
    MAIN_STORE,
41
38
    )
 
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 (
 
43
    IBugTaskSet,
44
44
    BugTaskSearchParams,
45
 
    IBugTaskSet,
46
45
    )
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 (
53
51
    IBranchCollection,
54
52
    InvalidFilter,
55
53
    )
 
54
from lp.code.interfaces.seriessourcepackagebranch import (
 
55
    IFindOfficialBranchLinks,
 
56
    )
 
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,
60
 
    )
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
    Diff,
 
68
    PreviewDiff,
 
69
    )
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 (
71
74
    Owner,
72
75
    Person,
 
76
    ValidPersonCache,
73
77
    )
74
78
from lp.registry.model.product import Product
75
79
from lp.registry.model.sourcepackagename import SourcePackageName
78
82
    load_referencing,
79
83
    load_related,
80
84
    )
81
 
from lp.services.database.decoratedresultset import DecoratedResultSet
82
85
from lp.services.propertycache import get_property_cache
83
86
 
84
87
 
123
126
        if exclude_from_search is None:
124
127
            exclude_from_search = []
125
128
        self._exclude_from_search = exclude_from_search
126
 
        self._user = None
127
129
 
128
130
    def count(self):
129
131
        """See `IBranchCollection`."""
130
132
        return self.getBranches(eager_load=False).count()
131
133
 
132
 
    def is_empty(self):
133
 
        """See `IBranchCollection`."""
134
 
        return self.getBranches(eager_load=False).is_empty()
135
 
 
136
134
    def ownerCounts(self):
137
135
        """See `IBranchCollection`."""
138
136
        is_team = Person.teamowner != None
219
217
        return [
220
218
            With("candidate_branches", SQL("SELECT id from scope_branches"))]
221
219
 
222
 
    @staticmethod
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
226
 
        preloaded/returned.
227
 
 
228
 
        """
229
 
        if len(branches) == 0:
230
 
            return
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
235
 
                UNION
236
 
                SELECT DISTINCT branch.stacked_on
237
 
                FROM stacked_on_branches_ids, Branch AS branch
238
 
                WHERE
239
 
                    branch.id = stacked_on_branches_ids.id AND
240
 
                    branch.stacked_on IS NOT NULL
241
 
            )
242
 
            SELECT id from stacked_on_branches_ids
243
 
            """ % ', '.join(
244
 
                ["(%s)" % quote(id)
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)]
251
 
        if user is None:
252
 
            collection = AnonymousBranchCollection(
253
 
                branch_filter_expressions=expressions)
254
 
        else:
255
 
            collection = VisibleBranchCollection(
256
 
                user=user, branch_filter_expressions=expressions)
257
 
        return list(collection.getBranches())
258
 
 
259
 
    @staticmethod
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))
273
233
        # associatedProductSeries
274
234
        # Imported here to avoid circular import.
275
235
        from lp.registry.model.productseries import ProductSeries
276
 
        for productseries in IStore(ProductSeries).find(
 
236
        for productseries in self.store.find(
277
237
            ProductSeries,
278
238
            ProductSeries.branchID.is_in(branch_ids)):
279
239
            cache = caches[productseries.branchID]
307
267
            branch_ids = set(branch.id for branch in rows)
308
268
            if not branch_ids:
309
269
                return
310
 
            GenericBranchCollection.preloadDataForBranches(rows)
 
270
            self._preloadDataForBranches(rows)
311
271
            load_related(Product, rows, ['productID'])
312
272
            # So far have only needed the persons for their canonical_url - no
313
273
            # need for validity etc in the /branches API call.
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)
336
287
        else:
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)
342
292
 
343
293
    def _naiveGetMergeProposals(self, statuses=None, for_branches=None,
344
 
                                target_branch=None, merged_revnos=None,
345
 
                                eager_load=False):
 
294
        target_branch=None, merged_revnos=None, eager_load=False):
 
295
 
 
296
        def do_eager_load(rows):
 
297
            branch_ids = set()
 
298
            person_ids = set()
 
299
            diff_ids = set()
 
300
            for mp in 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)
 
306
            if not branch_ids:
 
307
                return
 
308
 
 
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),
 
314
                ))
 
315
 
 
316
            # Pre-load PreviewDiffs and Diffs.
 
317
            list(self.store.find(
 
318
                (PreviewDiff, Diff),
 
319
                PreviewDiff.id.is_in(diff_ids),
 
320
                Diff.id == PreviewDiff.diff_id))
 
321
 
 
322
            branches = set(
 
323
                self.store.find(Branch, Branch.id.is_in(branch_ids)))
 
324
            self._preloadDataForBranches(branches)
 
325
 
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:
374
354
            return resultset
375
355
        else:
376
 
            loader = partial(
377
 
                BranchMergeProposal.preloadDataForBMPs, user=self._user)
378
 
            return DecoratedResultSet(resultset, pre_iter_hook=loader)
 
356
            return DecoratedResultSet(resultset, pre_iter_hook=do_eager_load)
379
357
 
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),
385
 
            *scope_expressions)
 
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)
405
 
        if not eager_load:
406
 
            return resultset
407
 
        else:
408
 
            loader = partial(
409
 
                BranchMergeProposal.preloadDataForBMPs, user=self._user)
410
 
            return DecoratedResultSet(resultset, pre_iter_hook=loader)
411
382
 
412
 
    def getMergeProposalsForPerson(self, person, status=None,
413
 
                                   eager_load=False):
 
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)
420
 
 
421
 
        if not eager_load:
422
 
            return resultset
423
 
        else:
424
 
            loader = partial(
425
 
                BranchMergeProposal.preloadDataForBMPs, user=self._user)
426
 
            return DecoratedResultSet(resultset, pre_iter_hook=loader)
 
389
        return owned.union(reviewing)
427
390
 
428
391
    def getMergeProposalsForReviewer(self, reviewer, status=None):
429
392
        """See `IBranchCollection`."""
746
709
 
747
710
    def _getBranchVisibilityExpression(self, branch_class=Branch):
748
711
        """Return the where clauses for visibility."""
749
 
        return [branch_class.transitively_private == False]
 
712
        return [branch_class.private == False]
750
713
 
751
714
    def _getCandidateBranchesWith(self):
752
715
        """Return WITH clauses defining candidate branches.
757
720
        # Anonymous users get public branches only.
758
721
        return [
759
722
            With("candidate_branches",
760
 
                SQL("""select id from scope_branches
761
 
                    where not transitively_private"""))
 
723
                SQL("select id from scope_branches where not private"))
762
724
            ]
763
725
 
764
726
 
838
800
            Select(Branch.id,
839
801
                   And(Branch.owner == TeamParticipation.teamID,
840
802
                       TeamParticipation.person == person,
841
 
                       Branch.transitively_private == True)),
 
803
                       Branch.private == True)),
842
804
            # Private branches the person is subscribed to, either directly or
843
805
            # indirectly.
844
806
            Select(Branch.id,
846
808
                       BranchSubscription.person ==
847
809
                       TeamParticipation.teamID,
848
810
                       TeamParticipation.person == person,
849
 
                       Branch.transitively_private == True)))
 
811
                       Branch.private == True)))
850
812
        return private_branches
851
813
 
852
814
    def _getBranchVisibilityExpression(self, branch_class=Branch):
855
817
        :param branch_class: The Branch class to use - permits using
856
818
            ClassAliases.
857
819
        """
858
 
        public_branches = branch_class.transitively_private == False
 
820
        public_branches = branch_class.private == False
859
821
        if self._private_branch_ids is None:
860
822
            # Public only.
861
823
            return [public_branches]
876
838
            # Really an anonymous sitation
877
839
            return [
878
840
                With("candidate_branches",
879
 
                    SQL("""
880
 
                        select id from scope_branches
881
 
                        where not transitively_private"""))
 
841
                    SQL("select id from scope_branches where not private"))
882
842
                ]
883
843
        return [
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)"""))
897
856
            ]
898
857
 
899
858
    def visibleByUser(self, person):