launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #09885
[Merge] lp:~abentley/launchpad/mp-by-revision-id into lp:launchpad
Aaron Bentley has proposed merging lp:~abentley/launchpad/mp-by-revision-id into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1020961 in Launchpad itself: "API cannot find merge proposal without branch"
https://bugs.launchpad.net/launchpad/+bug/1020961
For more details, see:
https://code.launchpad.net/~abentley/launchpad/mp-by-revision-id/+merge/114693
= Summary =
Fix bug #1020961: API cannot find merge proposal without branch
== Proposed fix ==
Add merged_revision parameter to GenericBranchCollection.getMergeProposals, add BranchSet.getMergeProposals and export via API.
== Pre-implementation notes ==
None
== LOC Rationale ==
I have a LOC credit of 1983
== Implementation details ==
This approach uses BranchRevision to find the association between a revision_id and a merge proposal. Another option would be a schema change to associate the merge proposal with a Revision.id or revision-id directly, but I don't belive that this API has enough traffic to warrant that.
== Tests ==
bin/test -vt TestGetMergeProposals
== Demo and Q/A ==
Using launchpadlib (potentially via lp-shell), use getMergeProposals with revision-id 'launchpad@xxxxxxxxxxxxxxxxx-20120222203340-sn3txswd2ch18ded'. The sole result should be https://code.qastaging.launchpad.net/~wallyworld/launchpad/sharing-picker-933823-2/+merge/94087
= 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/testing/factory.py
lib/lp/code/model/branch.py
lib/lp/_schema_circular_imports.py
lib/lp/code/model/tests/test_branch.py
--
https://code.launchpad.net/~abentley/launchpad/mp-by-revision-id/+merge/114693
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/mp-by-revision-id into lp:launchpad.
=== modified file 'lib/lp/_schema_circular_imports.py'
--- lib/lp/_schema_circular_imports.py 2012-06-19 16:09:38 +0000
+++ lib/lp/_schema_circular_imports.py 2012-07-12 18:00:32 +0000
@@ -59,7 +59,10 @@
)
from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob
from lp.buildmaster.interfaces.buildqueue import IBuildQueue
-from lp.code.interfaces.branch import IBranch
+from lp.code.interfaces.branch import (
+ IBranch,
+ IBranchSet,
+ )
from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
from lp.code.interfaces.branchmergequeue import IBranchMergeQueue
from lp.code.interfaces.branchsubscription import IBranchSubscription
@@ -263,6 +266,9 @@
patch_collection_return_type(
IBranch, 'getMergeProposals', IBranchMergeProposal)
+patch_collection_return_type(
+ IBranchSet, 'getMergeProposals', IBranchMergeProposal)
+
IBranchMergeProposal['getComment'].queryTaggedValue(
LAZR_WEBSERVICE_EXPORTED)['return_type'].schema = ICodeReviewComment
IBranchMergeProposal['createComment'].queryTaggedValue(
=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py 2012-07-06 03:33:59 +0000
+++ lib/lp/code/interfaces/branch.py 2012-07-12 18:00:32 +0000
@@ -1405,6 +1405,20 @@
branches.
"""
+ @operation_returns_collection_of(Interface)
+ @call_with(visible_by_user=REQUEST_USER)
+ @operation_parameters(merged_revision=TextLine())
+ @export_read_operation()
+ @operation_for_version("devel")
+ def getMergeProposals(merged_revision, visible_by_user=None):
+ """Return the merge proposals that resulted in this revision.
+
+ :param merged_revision: The revision_id of the revision that resulted
+ from this merge proposal.
+ :param visible_by_user: The user to whom the proposals must be
+ visible. If None, only public proposals will be returned.
+ """
+
class IBranchListingQueryOptimiser(Interface):
"""Interface for a helper utility to do efficient queries for branches.
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2012-07-11 09:43:04 +0000
+++ lib/lp/code/model/branch.py 2012-07-12 18:00:32 +0000
@@ -1532,6 +1532,11 @@
'person_name': person.displayname,
'visible_branches': visible_branches}
+ def getMergeProposals(self, merged_revision, visible_by_user=None):
+ """See IBranchSet."""
+ collection = getUtility(IAllBranches).visibleByUser(visible_by_user)
+ return collection.getMergeProposals(merged_revision=merged_revision)
+
def update_trigger_modified_fields(branch):
"""Make the trigger updated fields reload when next accessed."""
=== modified file 'lib/lp/code/model/branchcollection.py'
--- lib/lp/code/model/branchcollection.py 2012-07-09 10:35:00 +0000
+++ lib/lp/code/model/branchcollection.py 2012-07-12 18:00:32 +0000
@@ -54,10 +54,12 @@
get_branch_privacy_filter,
)
from lp.code.model.branchmergeproposal import BranchMergeProposal
+from lp.code.model.branchrevision import BranchRevision
from lp.code.model.branchsubscription import BranchSubscription
from lp.code.model.codeimport import CodeImport
from lp.code.model.codereviewcomment import CodeReviewComment
from lp.code.model.codereviewvote import CodeReviewVoteReference
+from lp.code.model.revision import Revision
from lp.code.model.seriessourcepackagebranch import SeriesSourcePackageBranch
from lp.registry.enums import PUBLIC_INFORMATION_TYPES
from lp.registry.model.distribution import Distribution
@@ -313,7 +315,7 @@
def getMergeProposals(self, statuses=None, for_branches=None,
target_branch=None, merged_revnos=None,
- eager_load=False):
+ merged_revision=None, eager_load=False):
"""See `IBranchCollection`."""
if for_branches is not None and not for_branches:
# We have an empty branches list, so we can shortcut.
@@ -324,10 +326,11 @@
elif (self._asymmetric_filter_expressions or
for_branches is not None or
target_branch is not None or
- merged_revnos is not None):
+ merged_revnos is not None or
+ merged_revision is not None):
return self._naiveGetMergeProposals(
statuses, for_branches, target_branch, merged_revnos,
- eager_load=eager_load)
+ merged_revision, eager_load=eager_load)
else:
# When examining merge proposals in a scope, this is a moderately
# effective set of constrained queries. It is not effective when
@@ -337,7 +340,7 @@
def _naiveGetMergeProposals(self, statuses=None, for_branches=None,
target_branch=None, merged_revnos=None,
- eager_load=False):
+ merged_revision=None, eager_load=False):
Target = ClassAlias(Branch, "target")
extra_tables = list(set(
self._tables.values() + self._asymmetric_tables.values()))
@@ -360,6 +363,15 @@
if merged_revnos is not None:
expressions.append(
BranchMergeProposal.merged_revno.is_in(merged_revnos))
+ if merged_revision is not None:
+ expressions.extend([
+ BranchMergeProposal.merged_revno == BranchRevision.sequence,
+ BranchRevision.revision_id == Revision.id,
+ BranchRevision.branch_id ==
+ BranchMergeProposal.target_branchID,
+ Revision.revision_id == merged_revision
+ ])
+ tables.extend([BranchRevision, Revision])
if statuses is not None:
expressions.append(
BranchMergeProposal.queue_status.is_in(statuses))
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py 2012-07-11 09:36:13 +0000
+++ lib/lp/code/model/tests/test_branch.py 2012-07-12 18:00:32 +0000
@@ -84,6 +84,7 @@
IFindOfficialBranchLinks,
)
from lp.code.model.branch import (
+ BranchSet,
ClearDependentBranch,
ClearOfficialPackageBranch,
ClearSeriesBranch,
@@ -150,6 +151,7 @@
TestCaseWithFactory,
time_counter,
ws_object,
+ WebServiceTestCase,
)
from lp.testing.factory import LaunchpadObjectFactory
from lp.testing.layers import (
@@ -2947,6 +2949,86 @@
new_product, get_policies_for_artifact(branch)[0].pillar)
+def make_proposal_and_branch_revision(factory, revno, revision_id,
+ userdata_target=False):
+ revision = factory.makeBranchRevision(revision_id=revision_id,
+ sequence=revno)
+ bmp = factory.makeBranchMergeProposal(merged_revno=revno,
+ target_branch=revision.branch)
+ if userdata_target:
+ naked_target = removeSecurityProxy(bmp.target_branch)
+ naked_target.information_type = InformationType.USERDATA
+ return bmp
+
+
+class TestGetMergeProposalsWS(WebServiceTestCase):
+
+ def test_getMergeProposals(self):
+ """getMergeProposals works as expected over the API."""
+ bmp = make_proposal_and_branch_revision(self.factory, 5, 'rev-id',
+ userdata_target=True)
+ transaction.commit()
+ user = removeSecurityProxy(bmp).target_branch.owner
+ service = self.factory.makeLaunchpadService(
+ user, version=self.ws_version)
+ result = service.branches.getMergeProposals(merged_revision='rev-id')
+ self.assertEqual([self.wsObject(bmp, user)], list(result))
+
+
+class TestGetMergeProposals(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestGetMergeProposals, self).setUp()
+ self.branch_set = BranchSet()
+
+ def test_getMergeProposals_with_no_merged_revno(self):
+ """Merge proposals with no merged revno are not found."""
+ make_proposal_and_branch_revision(self.factory, None, 'rev-id')
+ result = self.branch_set.getMergeProposals(merged_revision='rev-id')
+ self.assertEqual([], list(result))
+
+ def test_getMergeProposals_with_any_merged_revno(self):
+ """Any arbitrary revno will connect a revid to a proposal."""
+ bmp = make_proposal_and_branch_revision(
+ self.factory, self.factory.getUniqueInteger(), 'rev-id')
+ result = self.branch_set.getMergeProposals(merged_revision='rev-id')
+ self.assertEqual([bmp], list(result))
+
+ def test_getMergeProposals_correct_merged_revno(self):
+ """Only proposals with the correct merged_revno match."""
+ bmp1 = make_proposal_and_branch_revision(self.factory, 4, 'rev-id')
+ bmp2 = make_proposal_and_branch_revision(self.factory, 5, 'other')
+ result = self.branch_set.getMergeProposals(merged_revision='rev-id')
+ self.assertEqual([bmp1], list(result))
+ result = self.branch_set.getMergeProposals(merged_revision='other')
+ self.assertEqual([bmp2], list(result))
+
+ def test_getMergeProposals_correct_branch(self):
+ """Only proposals with the correct branch match."""
+ bmp1 = make_proposal_and_branch_revision(self.factory, 5, 'rev-id')
+ make_proposal_and_branch_revision(self.factory, 5, 'other')
+ result = self.branch_set.getMergeProposals(merged_revision='rev-id')
+ self.assertEqual([bmp1], list(result))
+
+ def test_getMergeProposals_skips_hidden(self):
+ """Proposals not visible to the user are skipped."""
+ make_proposal_and_branch_revision(
+ self.factory, 5, 'rev-id', userdata_target=True)
+ result = self.branch_set.getMergeProposals(merged_revision='rev-id',
+ visible_by_user=self.factory.makePerson())
+ self.assertEqual([], list(result))
+
+ def test_getMergeProposals_shows_visible_userdata(self):
+ """Proposals visible to the user are listed."""
+ bmp = make_proposal_and_branch_revision(
+ self.factory, 5, 'rev-id', userdata_target=True)
+ result = self.branch_set.getMergeProposals(merged_revision='rev-id',
+ visible_by_user=bmp.target_branch.owner)
+ self.assertEqual([bmp], list(result))
+
+
class TestScheduleDiffUpdates(TestCaseWithFactory):
layer = DatabaseFunctionalLayer
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2012-07-03 10:29:53 +0000
+++ lib/lp/testing/factory.py 2012-07-12 18:00:32 +0000
@@ -1441,7 +1441,7 @@
product=None, initial_comment=None,
source_branch=None, preview_diff=None,
date_created=None, description=None,
- reviewer=None):
+ reviewer=None, merged_revno=None):
"""Create a proposal to merge based on anonymous branches."""
if target_branch is not None:
target = target_branch.target
@@ -1475,6 +1475,7 @@
date_created=date_created)
unsafe_proposal = removeSecurityProxy(proposal)
+ unsafe_proposal.merged_revno = merged_revno
if preview_diff is not None:
unsafe_proposal.preview_diff = preview_diff
if (set_state is None or
@@ -1638,8 +1639,10 @@
'', parent.revision_id, None, None, None)
branch.updateScannedDetails(parent, sequence)
- def makeBranchRevision(self, branch, revision_id=None, sequence=None,
+ def makeBranchRevision(self, branch=None, revision_id=None, sequence=None,
parent_ids=None, revision_date=None):
+ if branch is None:
+ branch = self.makeBranch()
revision = self.makeRevision(
rev_id=revision_id, parent_ids=parent_ids,
revision_date=revision_date)
Follow ups