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
1=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
2--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-11-09 15:07:38 +0000
3+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-11-18 16:50:14 +0000
4@@ -169,6 +169,8 @@
5 IBranch, '_createMergeProposal', 'target_branch', IBranch)
6 patch_plain_parameter_type(
7 IBranch, '_createMergeProposal', 'prerequisite_branch', IBranch)
8+patch_collection_return_type(
9+ IBranch, 'getMergeProposals', IBranchMergeProposal)
10
11 IBranchMergeProposal['getComment'].queryTaggedValue(
12 LAZR_WEBSERVICE_EXPORTED)['return_type'].schema = ICodeReviewComment
13
14=== modified file 'lib/lp/code/interfaces/branch.py'
15--- lib/lp/code/interfaces/branch.py 2010-11-04 19:59:02 +0000
16+++ lib/lp/code/interfaces/branch.py 2010-11-18 16:50:14 +0000
17@@ -39,6 +39,7 @@
18 exported,
19 mutator_for,
20 operation_parameters,
21+ operation_returns_collection_of,
22 operation_returns_entry,
23 REQUEST_USER,
24 )
25@@ -75,6 +76,7 @@
26 )
27 from lp.code.enums import (
28 BranchLifecycleStatus,
29+ BranchMergeProposalStatus,
30 BranchSubscriptionDiffSize,
31 BranchSubscriptionNotificationLevel,
32 CodeReviewNotificationLevel,
33@@ -585,6 +587,19 @@
34 :param review_requests: An optional list of (`Person`, review_type).
35 """
36
37+ @operation_parameters(
38+ status=List(
39+ title=_("A list of merge proposal statuses to filter by."),
40+ value_type=Choice(vocabulary=BranchMergeProposalStatus)),
41+ merged_revnos=List(Int(
42+ title=_('The target-branch revno of the merge.'))))
43+ @call_with(visible_by_user=REQUEST_USER)
44+ @operation_returns_collection_of(Interface) # Really IBranchMergeProposal.
45+ @export_read_operation()
46+ def getMergeProposals(status=None, visible_by_user=None,
47+ merged_revnos=None):
48+ """Return matching BranchMergeProposals."""
49+
50 def scheduleDiffUpdates():
51 """Create UpdatePreviewDiffJobs for this branch's targets."""
52
53
54=== modified file 'lib/lp/code/model/branch.py'
55--- lib/lp/code/model/branch.py 2010-11-09 08:43:34 +0000
56+++ lib/lp/code/model/branch.py 2010-11-18 16:50:14 +0000
57@@ -360,8 +360,9 @@
58 BranchMergeProposal.queue_status NOT IN %s
59 """ % sqlvalues(self, BRANCH_MERGE_PROPOSAL_FINAL_STATES))
60
61- def getMergeProposals(self, status=None, visible_by_user=None):
62- """See `IHasMergeProposals`."""
63+ def getMergeProposals(self, status=None, visible_by_user=None,
64+ merged_revnos=None):
65+ """See `IBranch`."""
66 if not status:
67 status = (
68 BranchMergeProposalStatus.CODE_APPROVED,
69@@ -369,7 +370,8 @@
70 BranchMergeProposalStatus.WORK_IN_PROGRESS)
71
72 collection = getUtility(IAllBranches).visibleByUser(visible_by_user)
73- return collection.getMergeProposals(status, target_branch=self)
74+ return collection.getMergeProposals(
75+ status, target_branch=self, merged_revnos=merged_revnos)
76
77 def isBranchMergeable(self, target_branch):
78 """See `IBranch`."""
79
80=== modified file 'lib/lp/code/model/branchcollection.py'
81--- lib/lp/code/model/branchcollection.py 2010-08-20 20:31:18 +0000
82+++ lib/lp/code/model/branchcollection.py 2010-11-18 16:50:14 +0000
83@@ -145,7 +145,7 @@
84 return self.store.using(*tables).find(Branch, *expressions)
85
86 def getMergeProposals(self, statuses=None, for_branches=None,
87- target_branch=None):
88+ target_branch=None, merged_revnos=None):
89 """See `IBranchCollection`."""
90 expressions = [
91 BranchMergeProposal.source_branchID.is_in(
92@@ -158,6 +158,9 @@
93 if target_branch is not None:
94 expressions.append(
95 BranchMergeProposal.target_branch == target_branch)
96+ if merged_revnos is not None:
97+ expressions.append(
98+ BranchMergeProposal.merged_revno.is_in(merged_revnos))
99 expressions.extend(self._getExtraMergeProposalExpressions())
100 if statuses is not None:
101 expressions.append(
102
103=== modified file 'lib/lp/code/model/tests/test_branch.py'
104--- lib/lp/code/model/tests/test_branch.py 2010-11-04 19:59:02 +0000
105+++ lib/lp/code/model/tests/test_branch.py 2010-11-18 16:50:14 +0000
106@@ -66,7 +66,6 @@
107 InvalidBranchMergeProposal,
108 InvalidMergeQueueConfig,
109 )
110-from lp.code.event.branchmergeproposal import NewBranchMergeProposalEvent
111 from lp.code.interfaces.branch import (
112 DEFAULT_BRANCH_STATUS_IN_LISTING,
113 IBranch,
114@@ -2757,7 +2756,8 @@
115 branch = self.factory.makeBranch()
116 config = simplejson.dumps({
117 'path': '/',
118- 'test': 'make test',})
119+ 'test': 'make test',
120+ })
121
122 with person_logged_in(branch.owner):
123 branch.setMergeQueueConfig(config)
124@@ -2815,6 +2815,19 @@
125 branch2 = ws_object(launchpad, db_branch)
126 self.assertEqual(branch2.merge_queue_config, configuration)
127
128+ def test_getMergeProposals_with_merged_revnos(self):
129+ """Specifying merged revnos selects the correct merge proposal."""
130+ mp = self.factory.makeBranchMergeProposal()
131+ launchpad = launchpadlib_for('test', mp.registrant,
132+ service_root="http://api.launchpad.dev:8085")
133+ with person_logged_in(mp.registrant):
134+ mp.markAsMerged(merged_revno=123)
135+ transaction.commit()
136+ target = ws_object(launchpad, mp.target_branch)
137+ mp = ws_object(launchpad, mp)
138+ self.assertEqual([mp], list(target.getMergeProposals(
139+ status=['Merged'], merged_revnos=[123])))
140+
141
142 def test_suite():
143 return TestLoader().loadTestsFromName(__name__)
144
145=== modified file 'lib/lp/code/model/tests/test_branchcollection.py'
146--- lib/lp/code/model/tests/test_branchcollection.py 2010-08-20 20:31:18 +0000
147+++ lib/lp/code/model/tests/test_branchcollection.py 2010-11-18 16:50:14 +0000
148@@ -40,6 +40,7 @@
149 from lp.code.model.branchcollection import GenericBranchCollection
150 from lp.registry.interfaces.pocket import PackagePublishingPocket
151 from lp.testing import (
152+ person_logged_in,
153 run_with_login,
154 TestCaseWithFactory,
155 )
156@@ -638,6 +639,24 @@
157 proposals = collection.getMergeProposals()
158 self.assertEqual([mp1], list(proposals))
159
160+ def test_merge_proposals_merging_revno(self):
161+ """Specifying merged_revnos selects the correct merge proposals."""
162+ target = self.factory.makeBranch()
163+ mp1 = self.factory.makeBranchMergeProposal(target_branch=target)
164+ mp2 = self.factory.makeBranchMergeProposal(target_branch=target)
165+ mp3 = self.factory.makeBranchMergeProposal(target_branch=target)
166+ with person_logged_in(target.owner):
167+ mp1.markAsMerged(123)
168+ mp2.markAsMerged(123)
169+ mp3.markAsMerged(321)
170+ collection = self.all_branches
171+ result = collection.getMergeProposals(
172+ target_branch=target, merged_revnos=[123])
173+ self.assertEqual(sorted([mp1, mp2]), sorted(result))
174+ result = collection.getMergeProposals(
175+ target_branch=target, merged_revnos=[123, 321])
176+ self.assertEqual(sorted([mp1, mp2, mp3]), sorted(result))
177+
178 def test_target_branch_private(self):
179 # The target branch must be in the branch collection, as must the
180 # source branch.