Merge lp:~abentley/launchpad/find-review into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 11949
Proposed branch: lp:~abentley/launchpad/find-review
Merge into: lp:launchpad
Diff against target: 180 lines (+60/-6)
6 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+2/-0)
lib/lp/code/interfaces/branch.py (+15/-0)
lib/lp/code/model/branch.py (+5/-3)
lib/lp/code/model/branchcollection.py (+4/-1)
lib/lp/code/model/tests/test_branch.py (+15/-2)
lib/lp/code/model/tests/test_branchcollection.py (+19/-0)
To merge this branch: bzr merge lp:~abentley/launchpad/find-review
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
j.c.sackett (community) code* Approve
Review via email: mp+41195@code.launchpad.net

Description of the change

= Summary =
Fix bug #677077: It should be possible to find the merge proposal that approved
a revision via the API

== Proposed fix ==
IBranch.getMergeProposals and add merged_revnos parameter.

== Pre-implementation notes ==
None

== Implementation details ==
IBranch derives from IHasMergeProposals, so it already provides
getMergeProposals. merged_revnos only makes sense in the context of a single
branch, because a revnos only refers to a specific revision in the context of a
branch. Therefore, merged_revnos could not be provided on IHasMergeProposals.
Therefore, getMergeProposals was exposed on IBranch.

merge_revnos is plural (not merged_revno) in order to support efficient access
to multiple merge proposals, should that be desired.

== Tests ==
bin/test -t test_getMergeProposals_with_merged_revnos -t test_merge_proposals_merging_revno

== Demo and Q/A ==
Write a commandline script that uses merged_revnos to get a merge proposal over
the API.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/model/branchcollection.py
  lib/lp/code/interfaces/branch.py
  lib/lp/code/model/branch.py
  lib/lp/code/model/tests/test_branchcollection.py
  lib/canonical/launchpad/interfaces/_schema_circular_imports.py
  lib/lp/code/model/tests/test_branch.py

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

Aaron--

This seems like a really nice feature to have in the API, particularly given recent reviewers conversations.

Also, thanks for explaining some of the pain you had to go through for circular imports on IRC. It was very informative.

> === modified file 'lib/lp/code/model/branchcollection.py'
> --- lib/lp/code/model/branchcollection.py 2010-08-20 20:31:18 +0000
> +++ lib/lp/code/model/branchcollection.py 2010-11-18 16:50:14 +0000
> @@ -145,7 +145,7 @@
> return self.store.using(*tables).find(Branch, *expressions)
>
> def getMergeProposals(self, statuses=None, for_branches=None,
> - target_branch=None):
> + target_branch=None, merged_revnos=None):
> """See `IBranchCollection`."""
> expressions = [
> BranchMergeProposal.source_branchID.is_in(

I brought the mismatch between this method definition and the interface definition with regards to merged_revnos as an argument up on IRC, and I buy your argument that it's not required b/c there's no security proxy and adding it would break other things.

It's not a blocker for merging, but a comment here to explain the difference or something wouldn't suck. :-)

> === modified file 'lib/lp/code/model/tests/test_branch.py'
> --- lib/lp/code/model/tests/test_branch.py 2010-11-04 19:59:02 +0000
> +++ lib/lp/code/model/tests/test_branch.py 2010-11-18 16:50:14 +0000
> @@ -66,7 +66,6 @@
> InvalidBranchMergeProposal,
> InvalidMergeQueueConfig,
> )
> -from lp.code.event.branchmergeproposal import NewBranchMergeProposalEvent
> from lp.code.interfaces.branch import (
> DEFAULT_BRANCH_STATUS_IN_LISTING,
> IBranch,
> @@ -2757,7 +2756,8 @@
> branch = self.factory.makeBranch()
> config = simplejson.dumps({
> 'path': '/',
> - 'test': 'make test',})
> + 'test': 'make test',
> + })
>
> with person_logged_in(branch.owner):
> branch.setMergeQueueConfig(config)

Thanks for doing that cleanup.

review: Approve (code*)
Revision history for this message
Curtis Hovey (sinzui) wrote :

I agree that this is good to land.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-11-09 15:07:38 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-11-18 16:50:14 +0000
@@ -169,6 +169,8 @@
169 IBranch, '_createMergeProposal', 'target_branch', IBranch)169 IBranch, '_createMergeProposal', 'target_branch', IBranch)
170patch_plain_parameter_type(170patch_plain_parameter_type(
171 IBranch, '_createMergeProposal', 'prerequisite_branch', IBranch)171 IBranch, '_createMergeProposal', 'prerequisite_branch', IBranch)
172patch_collection_return_type(
173 IBranch, 'getMergeProposals', IBranchMergeProposal)
172174
173IBranchMergeProposal['getComment'].queryTaggedValue(175IBranchMergeProposal['getComment'].queryTaggedValue(
174 LAZR_WEBSERVICE_EXPORTED)['return_type'].schema = ICodeReviewComment176 LAZR_WEBSERVICE_EXPORTED)['return_type'].schema = ICodeReviewComment
175177
=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py 2010-11-04 19:59:02 +0000
+++ lib/lp/code/interfaces/branch.py 2010-11-18 16:50:14 +0000
@@ -39,6 +39,7 @@
39 exported,39 exported,
40 mutator_for,40 mutator_for,
41 operation_parameters,41 operation_parameters,
42 operation_returns_collection_of,
42 operation_returns_entry,43 operation_returns_entry,
43 REQUEST_USER,44 REQUEST_USER,
44 )45 )
@@ -75,6 +76,7 @@
75 )76 )
76from lp.code.enums import (77from lp.code.enums import (
77 BranchLifecycleStatus,78 BranchLifecycleStatus,
79 BranchMergeProposalStatus,
78 BranchSubscriptionDiffSize,80 BranchSubscriptionDiffSize,
79 BranchSubscriptionNotificationLevel,81 BranchSubscriptionNotificationLevel,
80 CodeReviewNotificationLevel,82 CodeReviewNotificationLevel,
@@ -585,6 +587,19 @@
585 :param review_requests: An optional list of (`Person`, review_type).587 :param review_requests: An optional list of (`Person`, review_type).
586 """588 """
587589
590 @operation_parameters(
591 status=List(
592 title=_("A list of merge proposal statuses to filter by."),
593 value_type=Choice(vocabulary=BranchMergeProposalStatus)),
594 merged_revnos=List(Int(
595 title=_('The target-branch revno of the merge.'))))
596 @call_with(visible_by_user=REQUEST_USER)
597 @operation_returns_collection_of(Interface) # Really IBranchMergeProposal.
598 @export_read_operation()
599 def getMergeProposals(status=None, visible_by_user=None,
600 merged_revnos=None):
601 """Return matching BranchMergeProposals."""
602
588 def scheduleDiffUpdates():603 def scheduleDiffUpdates():
589 """Create UpdatePreviewDiffJobs for this branch's targets."""604 """Create UpdatePreviewDiffJobs for this branch's targets."""
590605
591606
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2010-11-09 08:43:34 +0000
+++ lib/lp/code/model/branch.py 2010-11-18 16:50:14 +0000
@@ -360,8 +360,9 @@
360 BranchMergeProposal.queue_status NOT IN %s360 BranchMergeProposal.queue_status NOT IN %s
361 """ % sqlvalues(self, BRANCH_MERGE_PROPOSAL_FINAL_STATES))361 """ % sqlvalues(self, BRANCH_MERGE_PROPOSAL_FINAL_STATES))
362362
363 def getMergeProposals(self, status=None, visible_by_user=None):363 def getMergeProposals(self, status=None, visible_by_user=None,
364 """See `IHasMergeProposals`."""364 merged_revnos=None):
365 """See `IBranch`."""
365 if not status:366 if not status:
366 status = (367 status = (
367 BranchMergeProposalStatus.CODE_APPROVED,368 BranchMergeProposalStatus.CODE_APPROVED,
@@ -369,7 +370,8 @@
369 BranchMergeProposalStatus.WORK_IN_PROGRESS)370 BranchMergeProposalStatus.WORK_IN_PROGRESS)
370371
371 collection = getUtility(IAllBranches).visibleByUser(visible_by_user)372 collection = getUtility(IAllBranches).visibleByUser(visible_by_user)
372 return collection.getMergeProposals(status, target_branch=self)373 return collection.getMergeProposals(
374 status, target_branch=self, merged_revnos=merged_revnos)
373375
374 def isBranchMergeable(self, target_branch):376 def isBranchMergeable(self, target_branch):
375 """See `IBranch`."""377 """See `IBranch`."""
376378
=== modified file 'lib/lp/code/model/branchcollection.py'
--- lib/lp/code/model/branchcollection.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/branchcollection.py 2010-11-18 16:50:14 +0000
@@ -145,7 +145,7 @@
145 return self.store.using(*tables).find(Branch, *expressions)145 return self.store.using(*tables).find(Branch, *expressions)
146146
147 def getMergeProposals(self, statuses=None, for_branches=None,147 def getMergeProposals(self, statuses=None, for_branches=None,
148 target_branch=None):148 target_branch=None, merged_revnos=None):
149 """See `IBranchCollection`."""149 """See `IBranchCollection`."""
150 expressions = [150 expressions = [
151 BranchMergeProposal.source_branchID.is_in(151 BranchMergeProposal.source_branchID.is_in(
@@ -158,6 +158,9 @@
158 if target_branch is not None:158 if target_branch is not None:
159 expressions.append(159 expressions.append(
160 BranchMergeProposal.target_branch == target_branch)160 BranchMergeProposal.target_branch == target_branch)
161 if merged_revnos is not None:
162 expressions.append(
163 BranchMergeProposal.merged_revno.is_in(merged_revnos))
161 expressions.extend(self._getExtraMergeProposalExpressions())164 expressions.extend(self._getExtraMergeProposalExpressions())
162 if statuses is not None:165 if statuses is not None:
163 expressions.append(166 expressions.append(
164167
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py 2010-11-04 19:59:02 +0000
+++ lib/lp/code/model/tests/test_branch.py 2010-11-18 16:50:14 +0000
@@ -66,7 +66,6 @@
66 InvalidBranchMergeProposal,66 InvalidBranchMergeProposal,
67 InvalidMergeQueueConfig,67 InvalidMergeQueueConfig,
68 )68 )
69from lp.code.event.branchmergeproposal import NewBranchMergeProposalEvent
70from lp.code.interfaces.branch import (69from lp.code.interfaces.branch import (
71 DEFAULT_BRANCH_STATUS_IN_LISTING,70 DEFAULT_BRANCH_STATUS_IN_LISTING,
72 IBranch,71 IBranch,
@@ -2757,7 +2756,8 @@
2757 branch = self.factory.makeBranch()2756 branch = self.factory.makeBranch()
2758 config = simplejson.dumps({2757 config = simplejson.dumps({
2759 'path': '/',2758 'path': '/',
2760 'test': 'make test',})2759 'test': 'make test',
2760 })
27612761
2762 with person_logged_in(branch.owner):2762 with person_logged_in(branch.owner):
2763 branch.setMergeQueueConfig(config)2763 branch.setMergeQueueConfig(config)
@@ -2815,6 +2815,19 @@
2815 branch2 = ws_object(launchpad, db_branch)2815 branch2 = ws_object(launchpad, db_branch)
2816 self.assertEqual(branch2.merge_queue_config, configuration)2816 self.assertEqual(branch2.merge_queue_config, configuration)
28172817
2818 def test_getMergeProposals_with_merged_revnos(self):
2819 """Specifying merged revnos selects the correct merge proposal."""
2820 mp = self.factory.makeBranchMergeProposal()
2821 launchpad = launchpadlib_for('test', mp.registrant,
2822 service_root="http://api.launchpad.dev:8085")
2823 with person_logged_in(mp.registrant):
2824 mp.markAsMerged(merged_revno=123)
2825 transaction.commit()
2826 target = ws_object(launchpad, mp.target_branch)
2827 mp = ws_object(launchpad, mp)
2828 self.assertEqual([mp], list(target.getMergeProposals(
2829 status=['Merged'], merged_revnos=[123])))
2830
28182831
2819def test_suite():2832def test_suite():
2820 return TestLoader().loadTestsFromName(__name__)2833 return TestLoader().loadTestsFromName(__name__)
28212834
=== modified file 'lib/lp/code/model/tests/test_branchcollection.py'
--- lib/lp/code/model/tests/test_branchcollection.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/model/tests/test_branchcollection.py 2010-11-18 16:50:14 +0000
@@ -40,6 +40,7 @@
40from lp.code.model.branchcollection import GenericBranchCollection40from lp.code.model.branchcollection import GenericBranchCollection
41from lp.registry.interfaces.pocket import PackagePublishingPocket41from lp.registry.interfaces.pocket import PackagePublishingPocket
42from lp.testing import (42from lp.testing import (
43 person_logged_in,
43 run_with_login,44 run_with_login,
44 TestCaseWithFactory,45 TestCaseWithFactory,
45 )46 )
@@ -638,6 +639,24 @@
638 proposals = collection.getMergeProposals()639 proposals = collection.getMergeProposals()
639 self.assertEqual([mp1], list(proposals))640 self.assertEqual([mp1], list(proposals))
640641
642 def test_merge_proposals_merging_revno(self):
643 """Specifying merged_revnos selects the correct merge proposals."""
644 target = self.factory.makeBranch()
645 mp1 = self.factory.makeBranchMergeProposal(target_branch=target)
646 mp2 = self.factory.makeBranchMergeProposal(target_branch=target)
647 mp3 = self.factory.makeBranchMergeProposal(target_branch=target)
648 with person_logged_in(target.owner):
649 mp1.markAsMerged(123)
650 mp2.markAsMerged(123)
651 mp3.markAsMerged(321)
652 collection = self.all_branches
653 result = collection.getMergeProposals(
654 target_branch=target, merged_revnos=[123])
655 self.assertEqual(sorted([mp1, mp2]), sorted(result))
656 result = collection.getMergeProposals(
657 target_branch=target, merged_revnos=[123, 321])
658 self.assertEqual(sorted([mp1, mp2, mp3]), sorted(result))
659
641 def test_target_branch_private(self):660 def test_target_branch_private(self):
642 # The target branch must be in the branch collection, as must the661 # The target branch must be in the branch collection, as must the
643 # source branch.662 # source branch.