Merge lp:~cjwatson/launchpad/git-activereviews into lp:launchpad
- git-activereviews
- Merge into devel
Proposed by Colin Watson
| Status: | Merged |
|---|---|
| Merged at revision: | 17500 |
| Proposed branch: | lp:~cjwatson/launchpad/git-activereviews |
| Merge into: | lp:launchpad |
| Diff against target: | 951 lines (+349/-104) 10 files modified lib/lp/code/browser/branchmergeproposallisting.py (+18/-24) lib/lp/code/browser/configure.zcml (+6/-0) lib/lp/code/browser/tests/test_branchmergeproposallisting.py (+206/-60) lib/lp/code/interfaces/gitref.py (+2/-1) lib/lp/code/interfaces/hasbranches.py (+20/-5) lib/lp/code/model/branchmergeproposal.py (+25/-7) lib/lp/code/model/gitcollection.py (+15/-0) lib/lp/code/model/hasbranches.py (+53/-3) lib/lp/code/templates/branchmergeproposal-listing.pt (+2/-2) lib/lp/code/templates/branchmergeproposal-macros.pt (+2/-2) |
| To merge this branch: | bzr merge lp:~cjwatson/launchpad/git-activereviews |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| William Grant (community) | code | Approve | |
| Review via email: | |||
Commit message
Extend merge proposal listings to cover Git, and add GitRef:
Description of the change
Extend merge proposal listings to cover Git, and add GitRef:
We should probably have GitRepository:
To post a comment you must log in.
Revision history for this message
| William Grant (wgrant) : | # |
review: Approve (code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
| 1 | === modified file 'lib/lp/code/browser/branchmergeproposallisting.py' |
| 2 | --- lib/lp/code/browser/branchmergeproposallisting.py 2014-12-08 00:32:30 +0000 |
| 3 | +++ lib/lp/code/browser/branchmergeproposallisting.py 2015-05-13 09:36:31 +0000 |
| 4 | @@ -1,4 +1,4 @@ |
| 5 | -# Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
| 6 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
| 7 | # GNU Affero General Public License version 3 (see the file LICENSE). |
| 8 | |
| 9 | """Base class view for branch merge proposal listings.""" |
| 10 | @@ -40,10 +40,6 @@ |
| 11 | BranchMergeProposalStatus, |
| 12 | CodeReviewVote, |
| 13 | ) |
| 14 | -from lp.code.interfaces.branchcollection import ( |
| 15 | - IAllBranches, |
| 16 | - IBranchCollection, |
| 17 | - ) |
| 18 | from lp.code.interfaces.branchmergeproposal import ( |
| 19 | BRANCH_MERGE_PROPOSAL_FINAL_STATES, |
| 20 | IBranchMergeProposal, |
| 21 | @@ -168,7 +164,7 @@ |
| 22 | |
| 23 | @cachedproperty |
| 24 | def proposals(self): |
| 25 | - """Return a list of BranchListingItems.""" |
| 26 | + """Return a list of BranchMergeProposalListingItems.""" |
| 27 | proposals = self._proposals_for_current_batch |
| 28 | return [self._createItem(proposal) for proposal in proposals] |
| 29 | |
| 30 | @@ -287,12 +283,11 @@ |
| 31 | |
| 32 | def getProposals(self): |
| 33 | """Get the proposals for the view.""" |
| 34 | - collection = IBranchCollection(self.context) |
| 35 | - collection = collection.visibleByUser(self.user) |
| 36 | - proposals = collection.getMergeProposals( |
| 37 | - [BranchMergeProposalStatus.CODE_APPROVED, |
| 38 | - BranchMergeProposalStatus.NEEDS_REVIEW], eager_load=True) |
| 39 | - return proposals |
| 40 | + return self.context.getMergeProposals( |
| 41 | + status=( |
| 42 | + BranchMergeProposalStatus.CODE_APPROVED, |
| 43 | + BranchMergeProposalStatus.NEEDS_REVIEW), |
| 44 | + visible_by_user=self.user, eager_load=True) |
| 45 | |
| 46 | def _getReviewGroup(self, proposal, votes, reviewer): |
| 47 | """One of APPROVED, MINE, TO_DO, CAN_DO, ARE_DOING, OTHER or WIP. |
| 48 | @@ -324,8 +319,8 @@ |
| 49 | return self.WIP |
| 50 | |
| 51 | if (reviewer is not None and |
| 52 | - (proposal.source_branch.owner == reviewer or |
| 53 | - (reviewer.inTeam(proposal.source_branch.owner) and |
| 54 | + (proposal.merge_source.owner == reviewer or |
| 55 | + (reviewer.inTeam(proposal.merge_source.owner) and |
| 56 | proposal.registrant == reviewer))): |
| 57 | return self.MINE |
| 58 | |
| 59 | @@ -437,15 +432,13 @@ |
| 60 | def _getReviewer(self): |
| 61 | return self.context |
| 62 | |
| 63 | - def _getCollection(self): |
| 64 | - return getUtility(IAllBranches) |
| 65 | - |
| 66 | - def getProposals(self): |
| 67 | + def getProposals(self, project=None): |
| 68 | """See `ActiveReviewsView`.""" |
| 69 | - collection = self._getCollection().visibleByUser(self.user) |
| 70 | - return collection.getMergeProposalsForPerson( |
| 71 | - self._getReviewer(), [BranchMergeProposalStatus.CODE_APPROVED, |
| 72 | - BranchMergeProposalStatus.NEEDS_REVIEW], eager_load=True) |
| 73 | + return self._getReviewer().getOwnedAndRequestedReviews( |
| 74 | + status=( |
| 75 | + BranchMergeProposalStatus.CODE_APPROVED, |
| 76 | + BranchMergeProposalStatus.NEEDS_REVIEW), |
| 77 | + visible_by_user=self.user, project=project, eager_load=True) |
| 78 | |
| 79 | |
| 80 | class PersonProductActiveReviewsView(PersonActiveReviewsView): |
| 81 | @@ -459,8 +452,9 @@ |
| 82 | def _getReviewer(self): |
| 83 | return self.context.person |
| 84 | |
| 85 | - def _getCollection(self): |
| 86 | - return getUtility(IAllBranches).inProduct(self.context.product) |
| 87 | + def getProposals(self): |
| 88 | + return super(PersonProductActiveReviewsView, self).getProposals( |
| 89 | + project=self.context.product) |
| 90 | |
| 91 | @property |
| 92 | def no_proposal_message(self): |
| 93 | |
| 94 | === modified file 'lib/lp/code/browser/configure.zcml' |
| 95 | --- lib/lp/code/browser/configure.zcml 2015-04-28 16:48:22 +0000 |
| 96 | +++ lib/lp/code/browser/configure.zcml 2015-05-13 09:36:31 +0000 |
| 97 | @@ -844,6 +844,12 @@ |
| 98 | name="+register-merge" |
| 99 | permission="launchpad.AnyPerson" |
| 100 | template="../templates/gitref-register-merge.pt"/> |
| 101 | + <browser:page |
| 102 | + for="lp.code.interfaces.gitref.IGitRef" |
| 103 | + class="lp.code.browser.branchmergeproposallisting.BranchActiveReviewsView" |
| 104 | + permission="zope.Public" |
| 105 | + name="+activereviews" |
| 106 | + template="../templates/active-reviews.pt"/> |
| 107 | <browser:menus |
| 108 | classes="GitRefContextMenu" |
| 109 | module="lp.code.browser.gitref"/> |
| 110 | |
| 111 | === modified file 'lib/lp/code/browser/tests/test_branchmergeproposallisting.py' |
| 112 | --- lib/lp/code/browser/tests/test_branchmergeproposallisting.py 2013-04-29 00:10:09 +0000 |
| 113 | +++ lib/lp/code/browser/tests/test_branchmergeproposallisting.py 2015-05-13 09:36:31 +0000 |
| 114 | @@ -1,4 +1,4 @@ |
| 115 | -# Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
| 116 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
| 117 | # GNU Affero General Public License version 3 (see the file LICENSE). |
| 118 | |
| 119 | """Unit tests for BranchMergeProposal listing views.""" |
| 120 | @@ -12,6 +12,7 @@ |
| 121 | from testtools.content_type import UTF8_TEXT |
| 122 | from testtools.matchers import Equals |
| 123 | import transaction |
| 124 | +from zope.component import getUtility |
| 125 | from zope.security.proxy import removeSecurityProxy |
| 126 | |
| 127 | from lp.app.enums import InformationType |
| 128 | @@ -23,8 +24,14 @@ |
| 129 | BranchMergeProposalStatus, |
| 130 | CodeReviewVote, |
| 131 | ) |
| 132 | +from lp.code.interfaces.gitref import IGitRef |
| 133 | +from lp.code.interfaces.gitrepository import ( |
| 134 | + GIT_FEATURE_FLAG, |
| 135 | + IGitRepositorySet, |
| 136 | + ) |
| 137 | from lp.registry.model.personproduct import PersonProduct |
| 138 | from lp.services.database.sqlbase import flush_database_caches |
| 139 | +from lp.services.features.testing import FeatureFixture |
| 140 | from lp.testing import ( |
| 141 | ANONYMOUS, |
| 142 | BrowserTestCase, |
| 143 | @@ -45,7 +52,55 @@ |
| 144 | _default = object() |
| 145 | |
| 146 | |
| 147 | -class TestProposalVoteSummary(TestCaseWithFactory): |
| 148 | +class BzrMixin: |
| 149 | + """Mixin for Bazaar-based tests.""" |
| 150 | + |
| 151 | + def _makeBranch(self, target=None, **kwargs): |
| 152 | + if target is not None: |
| 153 | + # This only handles projects at the moment. |
| 154 | + kwargs["product"] = target |
| 155 | + return self.factory.makeBranch(**kwargs) |
| 156 | + |
| 157 | + def _makePackageBranch(self, **kwargs): |
| 158 | + return self.factory.makePackageBranch(**kwargs) |
| 159 | + |
| 160 | + def _makeStackedOnBranchChain(self, target=None, **kwargs): |
| 161 | + if target is not None: |
| 162 | + # This only handles projects at the moment. |
| 163 | + kwargs["product"] = target |
| 164 | + return self.factory.makeStackedOnBranchChain(**kwargs) |
| 165 | + |
| 166 | + def _makeBranchMergeProposal(self, target=None, merge_target=None, |
| 167 | + **kwargs): |
| 168 | + # This only handles projects at the moment. |
| 169 | + return self.factory.makeBranchMergeProposal( |
| 170 | + product=target, target_branch=merge_target, **kwargs) |
| 171 | + |
| 172 | + |
| 173 | +class GitMixin: |
| 174 | + """Mixin for Git-based tests.""" |
| 175 | + |
| 176 | + def setUp(self, user=ANONYMOUS): |
| 177 | + super(GitMixin, self).setUp(user=user) |
| 178 | + self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"})) |
| 179 | + |
| 180 | + def _makeBranch(self, **kwargs): |
| 181 | + return self.factory.makeGitRefs(**kwargs)[0] |
| 182 | + |
| 183 | + def _makePackageBranch(self, **kwargs): |
| 184 | + dsp = self.factory.makeDistributionSourcePackage() |
| 185 | + return self.factory.makeGitRefs(target=dsp, **kwargs)[0] |
| 186 | + |
| 187 | + def _makeStackedOnBranchChain(self, depth=None, **kwargs): |
| 188 | + # Git doesn't have stacked branches. Just make an ordinary reference. |
| 189 | + return self._makeBranch(**kwargs) |
| 190 | + |
| 191 | + def _makeBranchMergeProposal(self, merge_target=None, **kwargs): |
| 192 | + return self.factory.makeBranchMergeProposalForGit( |
| 193 | + target_ref=merge_target, **kwargs) |
| 194 | + |
| 195 | + |
| 196 | +class TestProposalVoteSummaryMixin: |
| 197 | """The vote summary shows a summary of the current votes.""" |
| 198 | |
| 199 | layer = DatabaseFunctionalLayer |
| 200 | @@ -53,7 +108,8 @@ |
| 201 | def setUp(self): |
| 202 | # Use an admin so we don't have to worry about launchpad.Edit |
| 203 | # permissions on the merge proposals for adding comments. |
| 204 | - TestCaseWithFactory.setUp(self, user="admin@canonical.com") |
| 205 | + super(TestProposalVoteSummaryMixin, self).setUp( |
| 206 | + user="admin@canonical.com") |
| 207 | |
| 208 | def _createComment(self, proposal, reviewer=None, vote=None, |
| 209 | comment=_default): |
| 210 | @@ -69,7 +125,7 @@ |
| 211 | def _get_vote_summary(self, proposal): |
| 212 | """Return the vote summary string for the proposal.""" |
| 213 | view = create_initialized_view( |
| 214 | - proposal.source_branch.owner, '+merges', rootsite='code') |
| 215 | + proposal.merge_source.owner, '+merges', rootsite='code') |
| 216 | batch_navigator = view.proposals |
| 217 | # There will only be one item in the list of proposals. |
| 218 | [listing_item] = batch_navigator.proposals |
| 219 | @@ -78,14 +134,14 @@ |
| 220 | |
| 221 | def test_no_votes_or_comments(self): |
| 222 | # If there are no votes or comments, then we show that. |
| 223 | - proposal = self.factory.makeBranchMergeProposal() |
| 224 | + proposal = self._makeBranchMergeProposal() |
| 225 | summary, comment_count = self._get_vote_summary(proposal) |
| 226 | self.assertEqual([], summary) |
| 227 | self.assertEqual(0, comment_count) |
| 228 | |
| 229 | def test_no_votes_with_comments(self): |
| 230 | # The comment count is shown. |
| 231 | - proposal = self.factory.makeBranchMergeProposal() |
| 232 | + proposal = self._makeBranchMergeProposal() |
| 233 | self._createComment(proposal) |
| 234 | summary, comment_count = self._get_vote_summary(proposal) |
| 235 | self.assertEqual([], summary) |
| 236 | @@ -93,7 +149,7 @@ |
| 237 | |
| 238 | def test_vote_without_comment(self): |
| 239 | # If there are no comments we don't show a count. |
| 240 | - proposal = self.factory.makeBranchMergeProposal() |
| 241 | + proposal = self._makeBranchMergeProposal() |
| 242 | self._createComment( |
| 243 | proposal, vote=CodeReviewVote.APPROVE, comment=None) |
| 244 | summary, comment_count = self._get_vote_summary(proposal) |
| 245 | @@ -104,7 +160,7 @@ |
| 246 | |
| 247 | def test_vote_with_comment(self): |
| 248 | # A vote with a comment counts as a vote and a comment. |
| 249 | - proposal = self.factory.makeBranchMergeProposal() |
| 250 | + proposal = self._makeBranchMergeProposal() |
| 251 | self._createComment(proposal, vote=CodeReviewVote.APPROVE) |
| 252 | summary, comment_count = self._get_vote_summary(proposal) |
| 253 | self.assertEqual( |
| 254 | @@ -114,7 +170,7 @@ |
| 255 | |
| 256 | def test_disapproval(self): |
| 257 | # Shown as Disapprove: <count>. |
| 258 | - proposal = self.factory.makeBranchMergeProposal() |
| 259 | + proposal = self._makeBranchMergeProposal() |
| 260 | self._createComment(proposal, vote=CodeReviewVote.DISAPPROVE) |
| 261 | summary, comment_count = self._get_vote_summary(proposal) |
| 262 | self.assertEqual( |
| 263 | @@ -124,7 +180,7 @@ |
| 264 | |
| 265 | def test_abstain(self): |
| 266 | # Shown as Abstain: <count>. |
| 267 | - proposal = self.factory.makeBranchMergeProposal() |
| 268 | + proposal = self._makeBranchMergeProposal() |
| 269 | transaction.commit() |
| 270 | self._createComment(proposal, vote=CodeReviewVote.ABSTAIN) |
| 271 | summary, comment_count = self._get_vote_summary(proposal) |
| 272 | @@ -135,7 +191,7 @@ |
| 273 | |
| 274 | def test_vote_ranking(self): |
| 275 | # Votes go from best to worst. |
| 276 | - proposal = self.factory.makeBranchMergeProposal() |
| 277 | + proposal = self._makeBranchMergeProposal() |
| 278 | self._createComment(proposal, vote=CodeReviewVote.DISAPPROVE) |
| 279 | self._createComment(proposal, vote=CodeReviewVote.APPROVE) |
| 280 | summary, comment_count = self._get_vote_summary(proposal) |
| 281 | @@ -158,7 +214,7 @@ |
| 282 | |
| 283 | def test_multiple_votes_for_type(self): |
| 284 | # Multiple votes of a type are aggregated in the summary. |
| 285 | - proposal = self.factory.makeBranchMergeProposal() |
| 286 | + proposal = self._makeBranchMergeProposal() |
| 287 | self._createComment(proposal, vote=CodeReviewVote.DISAPPROVE) |
| 288 | self._createComment(proposal, vote=CodeReviewVote.APPROVE) |
| 289 | self._createComment(proposal, vote=CodeReviewVote.DISAPPROVE) |
| 290 | @@ -178,6 +234,16 @@ |
| 291 | self.assertEqual(4, comment_count) |
| 292 | |
| 293 | |
| 294 | +class TestProposalVoteSummaryBzr( |
| 295 | + TestProposalVoteSummaryMixin, BzrMixin, TestCaseWithFactory): |
| 296 | + """Test the vote summary for Bazaar.""" |
| 297 | + |
| 298 | + |
| 299 | +class TestProposalVoteSummaryGit( |
| 300 | + TestProposalVoteSummaryMixin, GitMixin, TestCaseWithFactory): |
| 301 | + """Test the vote summary for Git.""" |
| 302 | + |
| 303 | + |
| 304 | class TestMerges(BrowserTestCase): |
| 305 | |
| 306 | layer = DatabaseFunctionalLayer |
| 307 | @@ -193,7 +259,7 @@ |
| 308 | package = self.factory.makeDistributionSourcePackage() |
| 309 | self.getViewBrowser(package, '+merges', rootsite='code') |
| 310 | |
| 311 | - def test_query_count(self): |
| 312 | + def test_query_count_bzr(self): |
| 313 | product = self.factory.makeProduct() |
| 314 | target = self.factory.makeBranch( |
| 315 | product=product, information_type=InformationType.USERDATA) |
| 316 | @@ -209,24 +275,51 @@ |
| 317 | product, '+merges', rootsite='code', user=product.owner) |
| 318 | self.assertThat(recorder, HasQueryCount(Equals(41))) |
| 319 | |
| 320 | - def test_productseries(self): |
| 321 | + def test_query_count_git(self): |
| 322 | + self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"})) |
| 323 | + product = self.factory.makeProduct() |
| 324 | + [target] = self.factory.makeGitRefs( |
| 325 | + target=product, information_type=InformationType.USERDATA) |
| 326 | + for i in range(7): |
| 327 | + [source] = self.factory.makeGitRefs( |
| 328 | + target=product, information_type=InformationType.USERDATA) |
| 329 | + self.factory.makeBranchMergeProposalForGit( |
| 330 | + source_ref=source, target_ref=target) |
| 331 | + flush_database_caches() |
| 332 | + with StormStatementRecorder() as recorder: |
| 333 | + self.getViewBrowser( |
| 334 | + product, '+merges', rootsite='code', user=product.owner) |
| 335 | + self.assertThat(recorder, HasQueryCount(Equals(38))) |
| 336 | + |
| 337 | + def test_productseries_bzr(self): |
| 338 | target = self.factory.makeBranch() |
| 339 | - unique_name = target.unique_name |
| 340 | with person_logged_in(target.product.owner): |
| 341 | target.product.development_focus.branch = target |
| 342 | + identity = target.identity |
| 343 | self.factory.makeBranchMergeProposal(target_branch=target) |
| 344 | view = self.getViewBrowser(target, '+merges', rootsite='code') |
| 345 | - self.assertIn(unique_name, view.contents) |
| 346 | - |
| 347 | - |
| 348 | -class ActiveReviewGroupsTest(TestCaseWithFactory): |
| 349 | - """Tests for groupings used in for active reviews.""" |
| 350 | + self.assertIn(identity, view.contents) |
| 351 | + |
| 352 | + def test_product_git(self): |
| 353 | + self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"})) |
| 354 | + [target] = self.factory.makeGitRefs() |
| 355 | + with person_logged_in(target.target.owner): |
| 356 | + getUtility(IGitRepositorySet).setDefaultRepository( |
| 357 | + target.target, target.repository) |
| 358 | + identity = target.identity |
| 359 | + self.factory.makeBranchMergeProposalForGit(target_ref=target) |
| 360 | + view = self.getViewBrowser(target, '+merges', rootsite='code') |
| 361 | + self.assertIn(identity, view.contents) |
| 362 | + |
| 363 | + |
| 364 | +class ActiveReviewGroupsTestMixin: |
| 365 | + """Tests for groupings used for active reviews.""" |
| 366 | |
| 367 | layer = DatabaseFunctionalLayer |
| 368 | |
| 369 | def setUp(self): |
| 370 | - super(ActiveReviewGroupsTest, self).setUp() |
| 371 | - self.bmp = self.factory.makeBranchMergeProposal( |
| 372 | + super(ActiveReviewGroupsTestMixin, self).setUp() |
| 373 | + self.bmp = self._makeBranchMergeProposal( |
| 374 | set_state=BranchMergeProposalStatus.NEEDS_REVIEW) |
| 375 | |
| 376 | def assertReviewGroupForReviewer(self, reviewer, group): |
| 377 | @@ -247,21 +340,21 @@ |
| 378 | |
| 379 | def test_approved(self): |
| 380 | # If the proposal is approved, then the group is approved. |
| 381 | - self.bmp = self.factory.makeBranchMergeProposal( |
| 382 | + self.bmp = self._makeBranchMergeProposal( |
| 383 | set_state=BranchMergeProposalStatus.CODE_APPROVED) |
| 384 | self.assertReviewGroupForReviewer(None, ActiveReviewsView.APPROVED) |
| 385 | |
| 386 | def test_work_in_progress(self): |
| 387 | # If the proposal is in progress, then the group is wip. |
| 388 | - self.bmp = self.factory.makeBranchMergeProposal( |
| 389 | + self.bmp = self._makeBranchMergeProposal( |
| 390 | set_state=BranchMergeProposalStatus.WORK_IN_PROGRESS) |
| 391 | self.assertReviewGroupForReviewer(None, ActiveReviewsView.WIP) |
| 392 | |
| 393 | - def test_source_branch_owner(self): |
| 394 | - # If the reviewer is the owner of the source branch, then the review |
| 395 | + def test_merge_source_owner(self): |
| 396 | + # If the reviewer is the owner of the merge source, then the review |
| 397 | # is MINE. This occurs whether or not the user is the logged in or |
| 398 | # not. |
| 399 | - reviewer = self.bmp.source_branch.owner |
| 400 | + reviewer = self.bmp.merge_source.owner |
| 401 | self.assertReviewGroupForReviewer(reviewer, ActiveReviewsView.MINE) |
| 402 | |
| 403 | def test_proposal_registrant(self): |
| 404 | @@ -271,13 +364,17 @@ |
| 405 | self.assertReviewGroupForReviewer(reviewer, ActiveReviewsView.OTHER) |
| 406 | |
| 407 | team = self.factory.makeTeam(self.bmp.registrant) |
| 408 | - removeSecurityProxy(self.bmp.source_branch).owner = team |
| 409 | + naked_merge_source = removeSecurityProxy(self.bmp.merge_source) |
| 410 | + if IGitRef.providedBy(naked_merge_source): |
| 411 | + naked_merge_source.repository.owner = team |
| 412 | + else: |
| 413 | + naked_merge_source.owner = team |
| 414 | self.assertReviewGroupForReviewer(reviewer, ActiveReviewsView.MINE) |
| 415 | |
| 416 | - def test_target_branch_owner(self): |
| 417 | - # For the target branch owner, it is to_do since they are the default |
| 418 | + def test_merge_target_owner(self): |
| 419 | + # For the merge target owner, it is to_do since they are the default |
| 420 | # reviewer. |
| 421 | - reviewer = self.bmp.target_branch.owner |
| 422 | + reviewer = self.bmp.merge_target.owner |
| 423 | self.assertReviewGroupForReviewer(reviewer, ActiveReviewsView.TO_DO) |
| 424 | |
| 425 | def test_group_pending_review(self): |
| 426 | @@ -299,7 +396,7 @@ |
| 427 | def test_review_done(self): |
| 428 | # If the logged in user has a completed review, then the review is |
| 429 | # ARE_DOING. |
| 430 | - reviewer = self.bmp.target_branch.owner |
| 431 | + reviewer = self.bmp.merge_target.owner |
| 432 | login_person(reviewer) |
| 433 | self.bmp.createComment( |
| 434 | reviewer, 'subject', vote=CodeReviewVote.APPROVE) |
| 435 | @@ -307,7 +404,17 @@ |
| 436 | reviewer, ActiveReviewsView.ARE_DOING) |
| 437 | |
| 438 | |
| 439 | -class TestBranchMergeProposalListingItem(TestCaseWithFactory): |
| 440 | +class ActiveReviewGroupsTestBzr( |
| 441 | + ActiveReviewGroupsTestMixin, BzrMixin, TestCaseWithFactory): |
| 442 | + """Tests for groupings used for active reviews for Bazaar.""" |
| 443 | + |
| 444 | + |
| 445 | +class ActiveReviewGroupsTestGit( |
| 446 | + ActiveReviewGroupsTestMixin, GitMixin, TestCaseWithFactory): |
| 447 | + """Tests for groupings used for active reviews for Git.""" |
| 448 | + |
| 449 | + |
| 450 | +class TestBranchMergeProposalListingItemMixin: |
| 451 | """Tests specifically relating to the BranchMergeProposalListingItem.""" |
| 452 | |
| 453 | layer = DatabaseFunctionalLayer |
| 454 | @@ -358,7 +465,17 @@ |
| 455 | self.assertEqual(bmp.date_created, item.sort_key) |
| 456 | |
| 457 | |
| 458 | -class ActiveReviewSortingTest(TestCaseWithFactory): |
| 459 | +class TestBranchMergeProposalListingItemBzr( |
| 460 | + TestBranchMergeProposalListingItemMixin, BzrMixin, TestCaseWithFactory): |
| 461 | + """Test BranchMergeProposalListingItem for Bazaar.""" |
| 462 | + |
| 463 | + |
| 464 | +class TestBranchMergeProposalListingItemGit( |
| 465 | + TestBranchMergeProposalListingItemMixin, GitMixin, TestCaseWithFactory): |
| 466 | + """Test BranchMergeProposalListingItem for Git.""" |
| 467 | + |
| 468 | + |
| 469 | +class ActiveReviewSortingTestMixin: |
| 470 | """Test the sorting of the active review groups.""" |
| 471 | |
| 472 | layer = DatabaseFunctionalLayer |
| 473 | @@ -366,14 +483,14 @@ |
| 474 | def test_oldest_first(self): |
| 475 | # The oldest requested reviews should be first. |
| 476 | product = self.factory.makeProduct() |
| 477 | - bmp1 = self.factory.makeBranchMergeProposal(product=product) |
| 478 | - login_person(bmp1.source_branch.owner) |
| 479 | + bmp1 = self._makeBranchMergeProposal(target=product) |
| 480 | + login_person(bmp1.merge_source.owner) |
| 481 | bmp1.requestReview(datetime(2009, 6, 1, tzinfo=pytz.UTC)) |
| 482 | - bmp2 = self.factory.makeBranchMergeProposal(product=product) |
| 483 | - login_person(bmp2.source_branch.owner) |
| 484 | + bmp2 = self._makeBranchMergeProposal(target=product) |
| 485 | + login_person(bmp2.merge_source.owner) |
| 486 | bmp2.requestReview(datetime(2009, 3, 1, tzinfo=pytz.UTC)) |
| 487 | - bmp3 = self.factory.makeBranchMergeProposal(product=product) |
| 488 | - login_person(bmp3.source_branch.owner) |
| 489 | + bmp3 = self._makeBranchMergeProposal(target=product) |
| 490 | + login_person(bmp3.merge_source.owner) |
| 491 | bmp3.requestReview(datetime(2009, 1, 1, tzinfo=pytz.UTC)) |
| 492 | login(ANONYMOUS) |
| 493 | view = create_initialized_view( |
| 494 | @@ -383,8 +500,18 @@ |
| 495 | [item.context for item in view.review_groups[view.OTHER]]) |
| 496 | |
| 497 | |
| 498 | -class ActiveReviewsWithPrivateBranches(TestCaseWithFactory): |
| 499 | - """Test the sorting of the active review groups.""" |
| 500 | +class ActiveReviewSortingTestBzr( |
| 501 | + ActiveReviewSortingTestMixin, BzrMixin, TestCaseWithFactory): |
| 502 | + """Test the sorting of the active review groups for Bazaar.""" |
| 503 | + |
| 504 | + |
| 505 | +class ActiveReviewSortingTestGit( |
| 506 | + ActiveReviewSortingTestMixin, GitMixin, TestCaseWithFactory): |
| 507 | + """Test the sorting of the active review groups for Git.""" |
| 508 | + |
| 509 | + |
| 510 | +class ActiveReviewsWithPrivateBranchesMixin: |
| 511 | + """Test reviews of private branches.""" |
| 512 | |
| 513 | layer = DatabaseFunctionalLayer |
| 514 | |
| 515 | @@ -392,32 +519,42 @@ |
| 516 | # Merge proposals against private branches are visible to |
| 517 | # the branch owner. |
| 518 | product = self.factory.makeProduct() |
| 519 | - branch = self.factory.makeBranch( |
| 520 | - product=product, information_type=InformationType.USERDATA) |
| 521 | + branch = self._makeBranch( |
| 522 | + target=product, information_type=InformationType.USERDATA) |
| 523 | with person_logged_in(removeSecurityProxy(branch).owner): |
| 524 | - mp = self.factory.makeBranchMergeProposal(target_branch=branch) |
| 525 | + mp = self._makeBranchMergeProposal(merge_target=branch) |
| 526 | view = create_initialized_view( |
| 527 | branch, name='+activereviews', rootsite='code') |
| 528 | self.assertEqual([mp], list(view.getProposals())) |
| 529 | |
| 530 | |
| 531 | -class PersonActiveReviewsPerformance(TestCaseWithFactory): |
| 532 | +class ActiveReviewsWithPrivateBranchesBzr( |
| 533 | + ActiveReviewsWithPrivateBranchesMixin, BzrMixin, TestCaseWithFactory): |
| 534 | + """Test reviews of private Bazaar branches.""" |
| 535 | + |
| 536 | + |
| 537 | +class ActiveReviewsWithPrivateBranchesGit( |
| 538 | + ActiveReviewsWithPrivateBranchesMixin, GitMixin, TestCaseWithFactory): |
| 539 | + """Test reviews of references in private Git repositories.""" |
| 540 | + |
| 541 | + |
| 542 | +class PersonActiveReviewsPerformanceMixin: |
| 543 | """Test the performance of the person's active reviews page.""" |
| 544 | |
| 545 | layer = LaunchpadFunctionalLayer |
| 546 | |
| 547 | def setupBMP(self, bmp): |
| 548 | self.factory.makePreviewDiff(merge_proposal=bmp) |
| 549 | - login_person(bmp.source_branch.owner) |
| 550 | + login_person(bmp.merge_source.owner) |
| 551 | bmp.requestReview() |
| 552 | |
| 553 | - def createUserBMP(self, reviewer=None, target_branch_owner=None): |
| 554 | - target_branch = None |
| 555 | - if target_branch_owner is not None: |
| 556 | - target_branch = self.factory.makePackageBranch( |
| 557 | - owner=target_branch_owner) |
| 558 | - bmp = self.factory.makeBranchMergeProposal( |
| 559 | - reviewer=reviewer, target_branch=target_branch) |
| 560 | + def createUserBMP(self, reviewer=None, merge_target_owner=None): |
| 561 | + merge_target = None |
| 562 | + if merge_target_owner is not None: |
| 563 | + merge_target = self._makePackageBranch( |
| 564 | + owner=merge_target_owner) |
| 565 | + bmp = self._makeBranchMergeProposal( |
| 566 | + reviewer=reviewer, merge_target=merge_target) |
| 567 | self.setupBMP(bmp) |
| 568 | return bmp |
| 569 | |
| 570 | @@ -431,10 +568,9 @@ |
| 571 | # Create one of the two types of BMP which will be displayed |
| 572 | # on a person's +activereviews page: |
| 573 | # - A BMP for which the person is the reviewer. |
| 574 | - # - A BMP for which the person is the owner of the target |
| 575 | - # branch. |
| 576 | + # - A BMP for which the person is the owner of the merge target. |
| 577 | if i % 2 == 0: |
| 578 | - self.createUserBMP(target_branch_owner=user) |
| 579 | + self.createUserBMP(merge_target_owner=user) |
| 580 | else: |
| 581 | self.createUserBMP(reviewer=user) |
| 582 | login_person(user) |
| 583 | @@ -457,9 +593,9 @@ |
| 584 | self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count))) |
| 585 | |
| 586 | def createProductBMP(self, product): |
| 587 | - target_branch = self.factory.makeStackedOnBranchChain(product=product) |
| 588 | - bmp = self.factory.makeBranchMergeProposal( |
| 589 | - product=product, target_branch=target_branch) |
| 590 | + merge_target = self._makeStackedOnBranchChain(target=product) |
| 591 | + bmp = self._makeBranchMergeProposal( |
| 592 | + target=product, merge_target=merge_target) |
| 593 | self.setupBMP(bmp) |
| 594 | return bmp |
| 595 | |
| 596 | @@ -491,3 +627,13 @@ |
| 597 | base_bmps + added_bmps) |
| 598 | self.assertEqual(base_bmps + added_bmps, view2.proposal_count) |
| 599 | self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count))) |
| 600 | + |
| 601 | + |
| 602 | +class PersonActiveReviewsPerformanceBzr( |
| 603 | + PersonActiveReviewsPerformanceMixin, BzrMixin, TestCaseWithFactory): |
| 604 | + """Test the performance of the person's active reviews page for Bazaar.""" |
| 605 | + |
| 606 | + |
| 607 | +class PersonActiveReviewsPerformanceGit( |
| 608 | + PersonActiveReviewsPerformanceMixin, GitMixin, TestCaseWithFactory): |
| 609 | + """Test the performance of the person's active reviews page for Git.""" |
| 610 | |
| 611 | === modified file 'lib/lp/code/interfaces/gitref.py' |
| 612 | --- lib/lp/code/interfaces/gitref.py 2015-04-24 12:58:46 +0000 |
| 613 | +++ lib/lp/code/interfaces/gitref.py 2015-05-13 09:36:31 +0000 |
| 614 | @@ -43,11 +43,12 @@ |
| 615 | BranchMergeProposalStatus, |
| 616 | GitObjectType, |
| 617 | ) |
| 618 | +from lp.code.interfaces.hasbranches import IHasMergeProposals |
| 619 | from lp.registry.interfaces.person import IPerson |
| 620 | from lp.services.webapp.interfaces import ITableBatchNavigator |
| 621 | |
| 622 | |
| 623 | -class IGitRef(Interface): |
| 624 | +class IGitRef(IHasMergeProposals): |
| 625 | """A reference in a Git repository.""" |
| 626 | |
| 627 | # XXX cjwatson 2015-01-19 bug=760849: "beta" is a lie to get WADL |
| 628 | |
| 629 | === modified file 'lib/lp/code/interfaces/hasbranches.py' |
| 630 | --- lib/lp/code/interfaces/hasbranches.py 2013-01-07 02:40:55 +0000 |
| 631 | +++ lib/lp/code/interfaces/hasbranches.py 2015-05-13 09:36:31 +0000 |
| 632 | @@ -1,4 +1,4 @@ |
| 633 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
| 634 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
| 635 | # GNU Affero General Public License version 3 (see the file LICENSE). |
| 636 | |
| 637 | """Interface definitions for IHas<code related bits>.""" |
| 638 | @@ -56,7 +56,7 @@ |
| 639 | title=_('Limit the branches to those modified since this date.'), |
| 640 | required=False)) |
| 641 | @call_with(visible_by_user=REQUEST_USER) |
| 642 | - @operation_returns_collection_of(Interface) # Really IBranch. |
| 643 | + @operation_returns_collection_of(Interface) # Really IBranch. |
| 644 | @export_read_operation() |
| 645 | @operation_for_version('beta') |
| 646 | def getBranches(status=None, visible_by_user=None, |
| 647 | @@ -88,7 +88,7 @@ |
| 648 | title=_("A list of merge proposal statuses to filter by."), |
| 649 | value_type=Choice(vocabulary=BranchMergeProposalStatus))) |
| 650 | @call_with(visible_by_user=REQUEST_USER) |
| 651 | - @operation_returns_collection_of(Interface) # Really IBranchMergeProposal. |
| 652 | + @operation_returns_collection_of(Interface) # Really IBranchMergeProposal. |
| 653 | @export_read_operation() |
| 654 | @operation_for_version('beta') |
| 655 | def getMergeProposals(status=None, visible_by_user=None): |
| 656 | @@ -115,7 +115,7 @@ |
| 657 | title=_("A list of merge proposal statuses to filter by."), |
| 658 | value_type=Choice(vocabulary=BranchMergeProposalStatus))) |
| 659 | @call_with(visible_by_user=REQUEST_USER) |
| 660 | - @operation_returns_collection_of(Interface) # Really IBranchMergeProposal. |
| 661 | + @operation_returns_collection_of(Interface) # Really IBranchMergeProposal. |
| 662 | @export_read_operation() |
| 663 | @operation_for_version('beta') |
| 664 | def getRequestedReviews(status=None, visible_by_user=None): |
| 665 | @@ -130,6 +130,21 @@ |
| 666 | :returns: A list of `IBranchMergeProposal`. |
| 667 | """ |
| 668 | |
| 669 | + def getOwnedAndRequestedReviews(status=None, visible_by_user=None, |
| 670 | + project=None): |
| 671 | + """Returns merge proposals for branches owned by a person, or where |
| 672 | + that person was asked to review. |
| 673 | + |
| 674 | + This does not include merge proposals that were requested from |
| 675 | + teams that the person is part of. If status is not passed then |
| 676 | + it will return proposals that are in the "Needs Review" state. |
| 677 | + |
| 678 | + :param status: A list of statuses to filter with. |
| 679 | + :param visible_by_user: Normally the user who is asking. |
| 680 | + :param project: Limit results to branches for this `IProduct`. |
| 681 | + :returns: A list of `IBranchMergeProposal`. |
| 682 | + """ |
| 683 | + |
| 684 | |
| 685 | class IHasCodeImports(Interface): |
| 686 | """Some things can have code imports that target them. |
| 687 | @@ -151,7 +166,7 @@ |
| 688 | schema=Interface) |
| 689 | ) |
| 690 | @call_with(registrant=REQUEST_USER) |
| 691 | - @export_factory_operation(Interface, []) # Really ICodeImport. |
| 692 | + @export_factory_operation(Interface, []) # Really ICodeImport. |
| 693 | @operation_for_version('beta') |
| 694 | def newCodeImport(registrant=None, branch_name=None, rcs_type=None, |
| 695 | url=None, cvs_root=None, cvs_module=None, owner=None): |
| 696 | |
| 697 | === modified file 'lib/lp/code/model/branchmergeproposal.py' |
| 698 | --- lib/lp/code/model/branchmergeproposal.py 2015-04-22 16:42:57 +0000 |
| 699 | +++ lib/lp/code/model/branchmergeproposal.py 2015-05-13 09:36:31 +0000 |
| 700 | @@ -92,7 +92,10 @@ |
| 701 | from lp.registry.interfaces.product import IProduct |
| 702 | from lp.registry.model.person import Person |
| 703 | from lp.services.config import config |
| 704 | -from lp.services.database.bulk import load_related |
| 705 | +from lp.services.database.bulk import ( |
| 706 | + load, |
| 707 | + load_related, |
| 708 | + ) |
| 709 | from lp.services.database.constants import ( |
| 710 | DEFAULT, |
| 711 | UTC_NOW, |
| 712 | @@ -1055,20 +1058,30 @@ |
| 713 | from lp.code.model.branch import Branch |
| 714 | from lp.code.model.branchcollection import GenericBranchCollection |
| 715 | from lp.code.model.gitcollection import GenericGitCollection |
| 716 | + from lp.code.model.gitref import GitRef |
| 717 | from lp.code.model.gitrepository import GitRepository |
| 718 | |
| 719 | ids = set() |
| 720 | source_branch_ids = set() |
| 721 | - source_git_repository_ids = set() |
| 722 | + git_ref_keys = set() |
| 723 | person_ids = set() |
| 724 | for mp in branch_merge_proposals: |
| 725 | ids.add(mp.id) |
| 726 | if mp.source_branchID is not None: |
| 727 | source_branch_ids.add(mp.source_branchID) |
| 728 | if mp.source_git_repositoryID is not None: |
| 729 | - source_git_repository_ids.add(mp.source_git_repositoryID) |
| 730 | + git_ref_keys.add( |
| 731 | + (mp.source_git_repositoryID, mp.source_git_path)) |
| 732 | + git_ref_keys.add( |
| 733 | + (mp.target_git_repositoryID, mp.target_git_path)) |
| 734 | + if mp.prerequisite_git_repositoryID is not None: |
| 735 | + git_ref_keys.add( |
| 736 | + (mp.prerequisite_git_repositoryID, |
| 737 | + mp.prerequisite_git_path)) |
| 738 | person_ids.add(mp.registrantID) |
| 739 | person_ids.add(mp.merge_reporterID) |
| 740 | + git_repository_ids = set( |
| 741 | + repository_id for repository_id, _ in git_ref_keys) |
| 742 | |
| 743 | branches = load_related( |
| 744 | Branch, branch_merge_proposals, ( |
| 745 | @@ -1078,9 +1091,11 @@ |
| 746 | GitRepository, branch_merge_proposals, ( |
| 747 | "target_git_repositoryID", "prerequisite_git_repositoryID", |
| 748 | "source_git_repositoryID")) |
| 749 | + load(GitRef, git_ref_keys) |
| 750 | # The stacked on branches are used to check branch visibility. |
| 751 | GenericBranchCollection.preloadVisibleStackedOnBranches( |
| 752 | branches, user) |
| 753 | + GenericGitCollection.preloadVisibleRepositories(repositories, user) |
| 754 | |
| 755 | if len(branches) == 0 and len(repositories) == 0: |
| 756 | return |
| 757 | @@ -1098,21 +1113,24 @@ |
| 758 | cache.preview_diff = previewdiff |
| 759 | |
| 760 | # Add source branch/repository owners' to the list of pre-loaded |
| 761 | - # persons. |
| 762 | + # persons. We need the target repository owner as well; unlike |
| 763 | + # branches, repository unique names aren't trigger-maintained. |
| 764 | person_ids.update( |
| 765 | branch.ownerID for branch in branches |
| 766 | if branch.id in source_branch_ids) |
| 767 | person_ids.update( |
| 768 | repository.owner_id for repository in repositories |
| 769 | - if repository.id in source_git_repository_ids) |
| 770 | + if repository.id in git_repository_ids) |
| 771 | |
| 772 | # Pre-load Person and ValidPersonCache. |
| 773 | list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( |
| 774 | person_ids, need_validity=True)) |
| 775 | |
| 776 | # Pre-load branches'/repositories' data. |
| 777 | - GenericBranchCollection.preloadDataForBranches(branches) |
| 778 | - GenericGitCollection.preloadDataForRepositories(repositories) |
| 779 | + if branches: |
| 780 | + GenericBranchCollection.preloadDataForBranches(branches) |
| 781 | + if repositories: |
| 782 | + GenericGitCollection.preloadDataForRepositories(repositories) |
| 783 | |
| 784 | |
| 785 | class BranchMergeProposalGetter: |
| 786 | |
| 787 | === modified file 'lib/lp/code/model/gitcollection.py' |
| 788 | --- lib/lp/code/model/gitcollection.py 2015-04-22 16:42:57 +0000 |
| 789 | +++ lib/lp/code/model/gitcollection.py 2015-05-13 09:36:31 +0000 |
| 790 | @@ -9,6 +9,7 @@ |
| 791 | ] |
| 792 | |
| 793 | from functools import partial |
| 794 | +from operator import attrgetter |
| 795 | |
| 796 | from lazr.uri import ( |
| 797 | InvalidURIError, |
| 798 | @@ -178,6 +179,20 @@ |
| 799 | return [] |
| 800 | |
| 801 | @staticmethod |
| 802 | + def preloadVisibleRepositories(repositories, user=None): |
| 803 | + """Preload visibility for the given list of repositories.""" |
| 804 | + if len(repositories) == 0: |
| 805 | + return |
| 806 | + expressions = [ |
| 807 | + GitRepository.id.is_in(map(attrgetter("id"), repositories))] |
| 808 | + if user is None: |
| 809 | + collection = AnonymousGitCollection(filter_expressions=expressions) |
| 810 | + else: |
| 811 | + collection = VisibleGitCollection( |
| 812 | + user=user, filter_expressions=expressions) |
| 813 | + return list(collection.getRepositories()) |
| 814 | + |
| 815 | + @staticmethod |
| 816 | def preloadDataForRepositories(repositories): |
| 817 | """Preload repositories' cached associated targets.""" |
| 818 | load_related(Distribution, repositories, ['distribution_id']) |
| 819 | |
| 820 | === modified file 'lib/lp/code/model/hasbranches.py' |
| 821 | --- lib/lp/code/model/hasbranches.py 2013-03-04 04:17:17 +0000 |
| 822 | +++ lib/lp/code/model/hasbranches.py 2015-05-13 09:36:31 +0000 |
| 823 | @@ -1,4 +1,4 @@ |
| 824 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
| 825 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
| 826 | # GNU Affero General Public License version 3 (see the file LICENSE). |
| 827 | |
| 828 | """Mixin classes to implement methods for IHas<code related bits>.""" |
| 829 | @@ -11,7 +11,10 @@ |
| 830 | 'HasRequestedReviewsMixin', |
| 831 | ] |
| 832 | |
| 833 | +from functools import partial |
| 834 | + |
| 835 | from zope.component import getUtility |
| 836 | +from zope.security.proxy import removeSecurityProxy |
| 837 | |
| 838 | from lp.code.enums import BranchMergeProposalStatus |
| 839 | from lp.code.interfaces.branch import DEFAULT_BRANCH_STATUS_IN_LISTING |
| 840 | @@ -20,6 +23,11 @@ |
| 841 | IBranchCollection, |
| 842 | ) |
| 843 | from lp.code.interfaces.branchtarget import IBranchTarget |
| 844 | +from lp.code.interfaces.gitcollection import ( |
| 845 | + IAllGitRepositories, |
| 846 | + IGitCollection, |
| 847 | + ) |
| 848 | +from lp.services.database.decoratedresultset import DecoratedResultSet |
| 849 | |
| 850 | |
| 851 | class HasBranchesMixin: |
| 852 | @@ -44,14 +52,28 @@ |
| 853 | def getMergeProposals(self, status=None, visible_by_user=None, |
| 854 | eager_load=False): |
| 855 | """See `IHasMergeProposals`.""" |
| 856 | + # Circular import. |
| 857 | + from lp.code.model.branchmergeproposal import BranchMergeProposal |
| 858 | + |
| 859 | if not status: |
| 860 | status = ( |
| 861 | BranchMergeProposalStatus.CODE_APPROVED, |
| 862 | BranchMergeProposalStatus.NEEDS_REVIEW, |
| 863 | BranchMergeProposalStatus.WORK_IN_PROGRESS) |
| 864 | |
| 865 | - collection = IBranchCollection(self).visibleByUser(visible_by_user) |
| 866 | - return collection.getMergeProposals(status, eager_load=eager_load) |
| 867 | + def _getProposals(interface): |
| 868 | + collection = removeSecurityProxy(interface(self)) |
| 869 | + collection = collection.visibleByUser(visible_by_user) |
| 870 | + return collection.getMergeProposals(status, eager_load=False) |
| 871 | + |
| 872 | + proposals = _getProposals(IBranchCollection).union( |
| 873 | + _getProposals(IGitCollection)) |
| 874 | + if not eager_load: |
| 875 | + return proposals |
| 876 | + else: |
| 877 | + loader = partial( |
| 878 | + BranchMergeProposal.preloadDataForBMPs, user=visible_by_user) |
| 879 | + return DecoratedResultSet(proposals, pre_iter_hook=loader) |
| 880 | |
| 881 | |
| 882 | class HasRequestedReviewsMixin: |
| 883 | @@ -66,6 +88,34 @@ |
| 884 | visible_by_user) |
| 885 | return visible_branches.getMergeProposalsForReviewer(self, status) |
| 886 | |
| 887 | + def getOwnedAndRequestedReviews(self, status=None, visible_by_user=None, |
| 888 | + project=None, eager_load=False): |
| 889 | + """See `IHasRequestedReviews`.""" |
| 890 | + # Circular import. |
| 891 | + from lp.code.model.branchmergeproposal import BranchMergeProposal |
| 892 | + |
| 893 | + if not status: |
| 894 | + status = (BranchMergeProposalStatus.NEEDS_REVIEW,) |
| 895 | + |
| 896 | + def _getProposals(collection): |
| 897 | + collection = collection.visibleByUser(visible_by_user) |
| 898 | + return collection.getMergeProposalsForPerson( |
| 899 | + self, status, eager_load=False) |
| 900 | + |
| 901 | + bzr_collection = removeSecurityProxy(getUtility(IAllBranches)) |
| 902 | + git_collection = removeSecurityProxy(getUtility(IAllGitRepositories)) |
| 903 | + if project is not None: |
| 904 | + bzr_collection = bzr_collection.inProduct(project) |
| 905 | + git_collection = git_collection.inProject(project) |
| 906 | + proposals = _getProposals(bzr_collection).union( |
| 907 | + _getProposals(git_collection)) |
| 908 | + if not eager_load: |
| 909 | + return proposals |
| 910 | + else: |
| 911 | + loader = partial( |
| 912 | + BranchMergeProposal.preloadDataForBMPs, user=visible_by_user) |
| 913 | + return DecoratedResultSet(proposals, pre_iter_hook=loader) |
| 914 | + |
| 915 | |
| 916 | class HasCodeImportsMixin: |
| 917 | |
| 918 | |
| 919 | === modified file 'lib/lp/code/templates/branchmergeproposal-listing.pt' |
| 920 | --- lib/lp/code/templates/branchmergeproposal-listing.pt 2009-09-14 02:04:02 +0000 |
| 921 | +++ lib/lp/code/templates/branchmergeproposal-listing.pt 2015-05-13 09:36:31 +0000 |
| 922 | @@ -35,10 +35,10 @@ |
| 923 | <td> |
| 924 | <a tal:attributes="href proposal/fmt:url"> |
| 925 | <strong> |
| 926 | - <tal:source-branch replace="proposal/source_branch/bzr_identity"/> |
| 927 | + <tal:merge-source replace="proposal/merge_source/identity"/> |
| 928 | </strong> |
| 929 | ⇒ |
| 930 | - <tal:source-branch replace="proposal/target_branch/bzr_identity"/> |
| 931 | + <tal:merge-target replace="proposal/merge_target/identity"/> |
| 932 | </a> |
| 933 | </td> |
| 934 | <td tal:attributes="class string:mergestatus${proposal/queue_status/name}" |
| 935 | |
| 936 | === modified file 'lib/lp/code/templates/branchmergeproposal-macros.pt' |
| 937 | --- lib/lp/code/templates/branchmergeproposal-macros.pt 2010-01-10 21:40:41 +0000 |
| 938 | +++ lib/lp/code/templates/branchmergeproposal-macros.pt 2015-05-13 09:36:31 +0000 |
| 939 | @@ -45,10 +45,10 @@ |
| 940 | <td> |
| 941 | <a tal:attributes="href proposal/fmt:url"> |
| 942 | <strong> |
| 943 | - <tal:source-branch replace="proposal/source_branch/bzr_identity"/> |
| 944 | + <tal:merge-source replace="proposal/merge_source/identity"/> |
| 945 | </strong> |
| 946 | ⇒ |
| 947 | - <tal:source-branch replace="proposal/target_branch/bzr_identity"/> |
| 948 | + <tal:merge-target replace="proposal/merge_target/identity"/> |
| 949 | </a> |
| 950 | </td> |
| 951 | <td> |