Merge lp:~james-w/launchpad/register-code-import into lp:launchpad
- register-code-import
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Aaron Bentley |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~james-w/launchpad/register-code-import |
Merge into: | lp:launchpad |
Prerequisite: | lp:~james-w/launchpad/code-imports-for-source-packages |
Diff against target: |
732 lines (+302/-35) 18 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+5/-1) lib/lp/code/errors.py (+1/-0) lib/lp/code/interfaces/branch.py (+8/-1) lib/lp/code/interfaces/branchtarget.py (+3/-1) lib/lp/code/interfaces/codeimport.py (+5/-1) lib/lp/code/interfaces/hasbranches.py (+49/-5) lib/lp/code/interfaces/webservice.py (+6/-1) lib/lp/code/mail/tests/test_codeimport.py (+1/-1) lib/lp/code/model/branchtarget.py (+20/-2) lib/lp/code/model/codeimport.py (+6/-3) lib/lp/code/model/hasbranches.py (+15/-1) lib/lp/code/model/tests/test_codeimport.py (+40/-0) lib/lp/code/stories/webservice/xx-branchmergeproposal.txt (+1/-1) lib/lp/code/stories/webservice/xx-code-import.txt (+129/-7) lib/lp/registry/interfaces/product.py (+4/-2) lib/lp/registry/interfaces/sourcepackage.py (+3/-2) lib/lp/registry/model/product.py (+3/-3) lib/lp/registry/model/sourcepackage.py (+3/-3) |
To merge this branch: | bzr merge lp:~james-w/launchpad/register-code-import |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Aaron Bentley (community) | Approve | ||
Review via email: mp+22668@code.launchpad.net |
Commit message
There is now a newCodeImport method on Product and SourcePackage in the API.
Description of the change
Hi,
This exposes the creation of code imports over the API.
It first adds an "owner" argument, so that we can specify
this through the API, with associated tests.
It then exposes the IBranchTarget.
over the API using a small interface and mixin, due to it being
on an adapter.
It then adds a bunch of tests for this, including error cases
where the user may specify bad information.
For the last bit I used webservice_error so that they would get
400 not 500 responses, and there won't be OOPSes generated for them.
To do this I had to add the exception classes that have the decorator
to the interfaces/
views for them.
Doing this I discovered that the existing webservice_error in lp.code
wasn't working, and in fact had a test asserting it wasn't working,
so I fixed that along the way.
Thanks,
James
James Westby (james-w) wrote : | # |
Aaron Bentley (abentley) wrote : | # |
It's nice of you to defer requesting a review until all tests are passing, but I think a lot of us don't observe that.
Was there a preimplementation call?
Was there any lint?
AIUI PEP8 requires that items within a class definition be separated by a single blank line, so a bunch of your webservice_
Why are you using export_
On IHasCodeImports, you claim that newCodeImport returns BranchMergeProp
What would you think about expressing xx-code-
James Westby (james-w) wrote : | # |
> It's nice of you to defer requesting a review until all tests are passing, but
> I think a lot of us don't observe that.
>
> Was there a preimplementation call?
No, but I chatted with wgrant about the strategy.
> Was there any lint?
I don't know, running "make lint" runs it on lots of unexpected files.
> AIUI PEP8 requires that items within a class definition be separated by a
> single blank line, so a bunch of your webservice_
> line in front of them.
Done.
> Why are you using export_
> on branchtarget?
That's not supposed to be there any more, deleted.
> On IHasCodeImports, you claim that newCodeImport returns BranchMergeProp
> I think this is inaccurate.
Fixed.
> What would you think about expressing xx-code-
> heading as a unit test? Failing that, it needs two blank lines separating it
> from the previous section.
I don't know how to do webservice tests as unit tests, if you can point me to
examples I will convert them.
Blank lines issue fixed anyway, and I updated the text to make the intent of
the tests clearer as well.
Thanks,
James
Aaron Bentley (abentley) wrote : | # |
On 04/05/2010 05:39 PM, James Westby wrote:
>> Was there any lint?
>
> I don't know, running "make lint" runs it on lots of unexpected files.
When there are no uncommitted changes, "make lint" compares your tree to
the parent branch to determine which files it runs against. This means
it will also lint files changed by your prerequisite branch. You parent
branch may also be out of date.
You can also run bin/lint.sh with the list of files you modified. If
you use pipelines and have the lp-review-body plugin installed, you can
use lp-propose (formerly lp-submit) and it will use the previous pipe to
determine which files to lint.
Anyhow, there was lots of lint:
$ make lint
= Launchpad lint =
Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.
Linting changed files:
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
lib/
== Pyflakes notices ==
lib/canonical/
43: 'IBranchTarget' imported but unused
lib/lp/
11: undefined name 'UnknownBranchT
lib/lp/
8: 'BranchMergePro
9: 'BranchCreatorN
9: 'IBranchSet' imported but unused
9: 'IBranch' imported but unused
9: 'BranchExists' imported but unused
9: 'BranchCreatorN
12: 'IBranchMergePr
13: 'IBranchSubscri
14: 'ICodeImport' imported but unused
15: 'ICodeReviewCom
16: 'ICodeReviewVot
17: 'IStaticDiff' imported but unused
17: 'IDiff' imported but unused
17: 'IPreviewDiff' imported but unused
lib/lp/
684: local variable 'displayname' is assigned to but never used
== Pylint notices ==
lib/canonical/
18: [F0401] Unable to import 'lazr.restful.
43: [W0611] Unused import IBranchTarget
lib/lp/
20: [F0401] Unable to import 'lazr.restful.
lib/lp/
442: [C0322, IBranch.setOwner] Operator not preceded by a space
title=_("The new owner of the branch."),
^
schema=
@export_
def setOwner(new_owner, user):
451: [C0322, IBranch.setTarget] Opera...
James Westby (james-w) wrote : | # |
On Tue, 06 Apr 2010 15:33:31 -0000, Aaron Bentley <email address hidden> wrote:
> On 04/05/2010 05:39 PM, James Westby wrote:
>
> >> Was there any lint?
> >
> > I don't know, running "make lint" runs it on lots of unexpected files.
>
> When there are no uncommitted changes, "make lint" compares your tree to
> the parent branch to determine which files it runs against. This means
> it will also lint files changed by your prerequisite branch. You parent
> branch may also be out of date.
I am using pipelines, so I would have thought it would run against the
previous pipe?
> You can also run bin/lint.sh with the list of files you modified. If
> you use pipelines and have the lp-review-body plugin installed, you can
> use lp-propose (formerly lp-submit) and it will use the previous pipe to
> determine which files to lint.
That's what I used (at least for one of these proposals,) but I didn't
have the extra plugin installed.
> lib/canonical/
> 43: 'IBranchTarget' imported but unused
Fixed.
> lib/lp/
> 11: undefined name 'UnknownBranchT
Fixed.
> lib/lp/
> 8: 'BranchMergePro
> 9: 'BranchCreatorN
> 9: 'IBranchSet' imported but unused
> 9: 'IBranch' imported but unused
> 9: 'BranchExists' imported but unused
> 9: 'BranchCreatorN
> 12: 'IBranchMergePr
> 13: 'IBranchSubscri
> 14: 'ICodeImport' imported but unused
> 15: 'ICodeReviewCom
> 16: 'ICodeReviewVot
> 17: 'IStaticDiff' imported but unused
> 17: 'IDiff' imported but unused
> 17: 'IPreviewDiff' imported but unused
Noise. This file is purely for importing.
> lib/lp/
> 684: local variable 'displayname' is assigned to but never used
Fixed.
> lib/lp/
> 442: [C0322, IBranch.setOwner] Operator not preceded by a space
> title=_("The new owner of the branch."),
> ^
> schema=IPerson))
> @export_
> def setOwner(new_owner, user):
> 451: [C0322, IBranch.setTarget] Operator not preceded by a space
> title=_("The project the branch belongs to."),
> ^
> schema=Interface, required=False), # Really IProduct
> source_
> title=_("The source package the branch belongs to."),
> schema=Interface, required=False)) # Really ISourcePackage
> @export_
> def setTarget(user, project=None, source_
> 511: [C0322, IBranch.
> by a space
> title=_("A person for which the reviewer status is in question."),
> ^
> schema=IPerson))
> @export_
> def isPersonTrusted
> 760: [C0322, IBranch.
> a space
> needs_review=
> ...
Aaron Bentley (abentley) wrote : | # |
On 04/07/2010 10:06 AM, James Westby wrote:
> On Tue, 06 Apr 2010 15:33:31 -0000, Aaron Bentley<email address hidden> wrote:
>> When there are no uncommitted changes, "make lint" compares your tree to
>> the parent branch to determine which files it runs against. This means
>> it will also lint files changed by your prerequisite branch. You parent
>> branch may also be out of date.
>
> I am using pipelines, so I would have thought it would run against the
> previous pipe?
No, I did not design it and it predates pipelines.
>> lib/lp/
>> 8: 'BranchMergePro
>> 9: 'BranchCreatorN
>> 9: 'IBranchSet' imported but unused
>> 9: 'IBranch' imported but unused
>> 9: 'BranchExists' imported but unused
>> 9: 'BranchCreatorN
>> 12: 'IBranchMergePr
>> 13: 'IBranchSubscri
>> 14: 'ICodeImport' imported but unused
>> 15: 'ICodeReviewCom
>> 16: 'ICodeReviewVot
>> 17: 'IStaticDiff' imported but unused
>> 17: 'IDiff' imported but unused
>> 17: 'IPreviewDiff' imported but unused
>
> Noise. This file is purely for importing.
It seems really weird to me. If it's there for external users, why isn't there an __all__? I'll chat with Leonard, but it doesn't need to block you.
Preview Diff
1 | === added directory 'lib/canonical/launchpad/apidoc' |
2 | === modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py' |
3 | --- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-04-09 15:46:09 +0000 |
4 | +++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-04-13 15:21:36 +0000 |
5 | @@ -45,7 +45,7 @@ |
6 | from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference |
7 | from lp.code.interfaces.diff import IPreviewDiff |
8 | from lp.code.interfaces.hasbranches import ( |
9 | - IHasBranches, IHasMergeProposals, IHasRequestedReviews) |
10 | + IHasBranches, IHasCodeImports, IHasMergeProposals, IHasRequestedReviews) |
11 | from lp.code.interfaces.sourcepackagerecipebuild import ( |
12 | ISourcePackageRecipeBuild) |
13 | from lp.registry.interfaces.distribution import IDistribution |
14 | @@ -130,6 +130,10 @@ |
15 | IHasMergeProposals, 'getMergeProposals', IBranchMergeProposal) |
16 | patch_collection_return_type( |
17 | IHasRequestedReviews, 'getRequestedReviews', IBranchMergeProposal) |
18 | +patch_entry_return_type( |
19 | + IHasCodeImports, 'newCodeImport', ICodeImport) |
20 | +patch_plain_parameter_type( |
21 | + IHasCodeImports, 'newCodeImport', 'owner', IPerson) |
22 | |
23 | # IBugTask |
24 | |
25 | |
26 | === modified file 'lib/lp/code/errors.py' |
27 | --- lib/lp/code/errors.py 2010-03-10 18:53:50 +0000 |
28 | +++ lib/lp/code/errors.py 2010-04-13 15:21:36 +0000 |
29 | @@ -41,6 +41,7 @@ |
30 | |
31 | class BranchMergeProposalExists(InvalidBranchMergeProposal): |
32 | """Raised if there is already a matching BranchMergeProposal.""" |
33 | + |
34 | webservice_error(400) #Bad request. |
35 | |
36 | |
37 | |
38 | === modified file 'lib/lp/code/interfaces/branch.py' |
39 | --- lib/lp/code/interfaces/branch.py 2010-04-09 02:08:39 +0000 |
40 | +++ lib/lp/code/interfaces/branch.py 2010-04-13 15:21:36 +0000 |
41 | @@ -49,7 +49,8 @@ |
42 | export_as_webservice_collection, export_as_webservice_entry, |
43 | export_destructor_operation, export_factory_operation, |
44 | export_operation_as, export_read_operation, export_write_operation, |
45 | - exported, mutator_for, operation_parameters, operation_returns_entry) |
46 | + exported, mutator_for, operation_parameters, operation_returns_entry, |
47 | + webservice_error) |
48 | |
49 | from canonical.config import config |
50 | |
51 | @@ -93,6 +94,8 @@ |
52 | class BranchExists(BranchCreationException): |
53 | """Raised when creating a branch that already exists.""" |
54 | |
55 | + webservice_error(400) |
56 | + |
57 | def __init__(self, existing_branch): |
58 | # XXX: TimPenhey 2009-07-12 bug=405214: This error |
59 | # message logic is incorrect, but the exact text is being tested |
60 | @@ -135,6 +138,8 @@ |
61 | the branch to a team that they are not a member of. |
62 | """ |
63 | |
64 | + webservice_error(400) |
65 | + |
66 | |
67 | class BranchCreationNoTeamOwnedJunkBranches(BranchCreationException): |
68 | """We forbid the creation of team-owned +junk branches. |
69 | @@ -158,6 +163,8 @@ |
70 | the branch to another user. |
71 | """ |
72 | |
73 | + webservice_error(400) |
74 | + |
75 | |
76 | class BranchTypeError(Exception): |
77 | """An operation cannot be performed for a particular branch type. |
78 | |
79 | === modified file 'lib/lp/code/interfaces/branchtarget.py' |
80 | --- lib/lp/code/interfaces/branchtarget.py 2010-04-09 02:08:39 +0000 |
81 | +++ lib/lp/code/interfaces/branchtarget.py 2010-04-13 15:21:36 +0000 |
82 | @@ -121,7 +121,7 @@ |
83 | """Get the BugTask for a given bug related to the branch target.""" |
84 | |
85 | def newCodeImport(registrant, branch_name, rcs_type, url=None, |
86 | - cvs_root=None, cvs_module=None): |
87 | + cvs_root=None, cvs_module=None, owner=None): |
88 | """Create a new code import for this target. |
89 | |
90 | :param registrant: the `IPerson` who should be recorded as creating |
91 | @@ -131,6 +131,8 @@ |
92 | :param url: the url to import from if the import isn't CVS. |
93 | :param cvs_root: if the import is from CVS the CVSROOT to import from. |
94 | :param cvs_module: if the import is from CVS the module to import. |
95 | + :param owner: the `IPerson` to own the resulting branch, or None to |
96 | + use registrant. |
97 | :returns: an `ICodeImport`. |
98 | :raises AssertionError: if supports_code_imports is False. |
99 | """ |
100 | |
101 | === modified file 'lib/lp/code/interfaces/codeimport.py' |
102 | --- lib/lp/code/interfaces/codeimport.py 2010-03-26 02:24:29 +0000 |
103 | +++ lib/lp/code/interfaces/codeimport.py 2010-04-13 15:21:36 +0000 |
104 | @@ -182,10 +182,14 @@ |
105 | """Interface representing the set of code imports.""" |
106 | |
107 | def new(registrant, target, branch_name, rcs_type, url=None, |
108 | - cvs_root=None, cvs_module=None, review_status=None): |
109 | + cvs_root=None, cvs_module=None, review_status=None, |
110 | + owner=None): |
111 | """Create a new CodeImport. |
112 | |
113 | :param target: An `IBranchTarget` that the code is associated with. |
114 | + :param owner: The `IPerson` to set as the owner of the branch, or |
115 | + None to use registrant. registrant must be a member of owner to |
116 | + do this. |
117 | """ |
118 | |
119 | def get(id): |
120 | |
121 | === modified file 'lib/lp/code/interfaces/hasbranches.py' |
122 | --- lib/lp/code/interfaces/hasbranches.py 2010-02-08 14:37:50 +0000 |
123 | +++ lib/lp/code/interfaces/hasbranches.py 2010-04-13 15:21:36 +0000 |
124 | @@ -1,25 +1,30 @@ |
125 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
126 | # GNU Affero General Public License version 3 (see the file LICENSE). |
127 | |
128 | +# pylint: disable-msg=E0213 |
129 | + |
130 | """Interface definitions for IHas<code related bits>.""" |
131 | |
132 | __metaclass__ = type |
133 | __all__ = [ |
134 | 'IHasBranches', |
135 | + 'IHasCodeImports', |
136 | 'IHasMergeProposals', |
137 | 'IHasRequestedReviews', |
138 | ] |
139 | |
140 | |
141 | from zope.interface import Interface |
142 | -from zope.schema import Choice, Datetime, List |
143 | +from zope.schema import Choice, Datetime, List, TextLine |
144 | |
145 | from canonical.launchpad import _ |
146 | -from lp.code.enums import BranchLifecycleStatus, BranchMergeProposalStatus |
147 | +from lp.code.enums import ( |
148 | + BranchLifecycleStatus, BranchMergeProposalStatus, RevisionControlSystems) |
149 | |
150 | from lazr.restful.declarations import ( |
151 | - REQUEST_USER, call_with, export_read_operation, operation_parameters, |
152 | - operation_returns_collection_of) |
153 | + REQUEST_USER, call_with, export_factory_operation, export_read_operation, |
154 | + operation_parameters, operation_returns_collection_of) |
155 | +from lazr.restful.fields import Reference |
156 | |
157 | |
158 | class IHasBranches(Interface): |
159 | @@ -98,7 +103,7 @@ |
160 | @operation_returns_collection_of(Interface) # Really IBranchMergeProposal. |
161 | @export_read_operation() |
162 | def getRequestedReviews(status=None, visible_by_user=None): |
163 | - """Returns merge proposals that a review was requested from the person. |
164 | + """Returns merge proposals where a person was asked to review. |
165 | |
166 | This does not include merge proposals that were requested from |
167 | teams that the person is part of. If status is not passed then |
168 | @@ -108,3 +113,42 @@ |
169 | :param visible_by_user: Normally the user who is asking. |
170 | :returns: A list of `IBranchMergeProposal`. |
171 | """ |
172 | + |
173 | + |
174 | +class IHasCodeImports(Interface): |
175 | + """Some things can have code imports that target them. |
176 | + |
177 | + This interface defines the common methods that for working with them. |
178 | + """ |
179 | + |
180 | + # In order to minimise dependancies the returns_collection is defined as |
181 | + # Interface here and defined fully in the circular imports file. |
182 | + |
183 | + @operation_parameters( |
184 | + branch_name=TextLine( |
185 | + title=_('Name of branch to create'), required=True), |
186 | + rcs_type=Choice(vocabulary=RevisionControlSystems, required=True), |
187 | + url=TextLine(title=_('Foreign VCS URL')), |
188 | + cvs_root=TextLine(title=_('CVS root URL')), |
189 | + cvs_module=TextLine(title=_('CVS module to import')), |
190 | + owner=Reference(title=_('Owner of the resulting branch'), |
191 | + schema=Interface) |
192 | + ) |
193 | + @call_with(registrant=REQUEST_USER) |
194 | + @export_factory_operation(Interface, []) # Really ICodeImport. |
195 | + def newCodeImport(registrant=None, branch_name=None, rcs_type=None, |
196 | + url=None, cvs_root=None, cvs_module=None, owner=None): |
197 | + """Create a new code import. |
198 | + |
199 | + :param registrant: The IPerson to record as the registrant of the |
200 | + import |
201 | + :param branch_name: The name of the branch to create. |
202 | + :param rcs_type: The type of the foreign VCS. |
203 | + :param url: The URL to import from if the VCS type uses a single URL |
204 | + (i.e. isn't CVS). |
205 | + :param cvs_root: The CVSROOT for a CVS import. |
206 | + :param cvs_module: The module to import for a CVS import. |
207 | + :param owner: Who should own the created branch, or None for it to |
208 | + be the same as the registrant, or the caller over the API. |
209 | + :returns: An instance of `ICodeImport`. |
210 | + """ |
211 | |
212 | === modified file 'lib/lp/code/interfaces/webservice.py' |
213 | --- lib/lp/code/interfaces/webservice.py 2010-03-16 19:04:48 +0000 |
214 | +++ lib/lp/code/interfaces/webservice.py 2010-04-13 15:21:36 +0000 |
215 | @@ -3,7 +3,12 @@ |
216 | |
217 | """All the interfaces that are exposed through the webservice.""" |
218 | |
219 | -from lp.code.interfaces.branch import IBranch, IBranchSet |
220 | +# The exceptions are imported so that they can produce the special |
221 | +# status code defined by webservice_error when they are raised. |
222 | +from lp.code.errors import BranchMergeProposalExists |
223 | +from lp.code.interfaces.branch import ( |
224 | + IBranch, IBranchSet, BranchCreatorNotMemberOfOwnerTeam, |
225 | + BranchCreatorNotOwner, BranchExists) |
226 | from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal |
227 | from lp.code.interfaces.branchsubscription import IBranchSubscription |
228 | from lp.code.interfaces.codeimport import ICodeImport |
229 | |
230 | === modified file 'lib/lp/code/mail/tests/test_codeimport.py' |
231 | --- lib/lp/code/mail/tests/test_codeimport.py 2010-04-13 15:21:34 +0000 |
232 | +++ lib/lp/code/mail/tests/test_codeimport.py 2010-04-13 15:21:36 +0000 |
233 | @@ -118,7 +118,7 @@ |
234 | series = self.factory.makeDistroSeries( |
235 | name='manic', distribution=distro) |
236 | fooix = self.factory.makeSourcePackage( |
237 | - sourcename='fooix', distroseries=series) |
238 | + sourcepackagename='fooix', distroseries=series) |
239 | # Eric needs to be logged in for the mail to be sent. |
240 | login_person(eric) |
241 | code_import = self.factory.makePackageCodeImport( |
242 | |
243 | === modified file 'lib/lp/code/model/branchtarget.py' |
244 | --- lib/lp/code/model/branchtarget.py 2010-04-13 15:21:34 +0000 |
245 | +++ lib/lp/code/model/branchtarget.py 2010-04-13 15:21:36 +0000 |
246 | @@ -37,10 +37,11 @@ |
247 | return self.context != other.context |
248 | |
249 | def newCodeImport(self, registrant, branch_name, rcs_type, url=None, |
250 | - cvs_root=None, cvs_module=None): |
251 | + cvs_root=None, cvs_module=None, owner=None): |
252 | + """See `IBranchTarget`.""" |
253 | return getUtility(ICodeImportSet).new( |
254 | registrant, self, branch_name, rcs_type, url=url, |
255 | - cvs_root=cvs_root, cvs_module=cvs_module) |
256 | + cvs_root=cvs_root, cvs_module=cvs_module, owner=owner) |
257 | |
258 | |
259 | class PackageBranchTarget(_BaseBranchTarget): |
260 | @@ -336,6 +337,23 @@ |
261 | branch.sourcepackagename = None |
262 | |
263 | |
264 | +class ProductSeriesBranchTarget(ProductBranchTarget): |
265 | + |
266 | + def __init__(self, productseries): |
267 | + ProductBranchTarget.__init__(self, productseries.product) |
268 | + self.productseries = productseries |
269 | + |
270 | + @property |
271 | + def context(self): |
272 | + """See `IBranchTarget`.""" |
273 | + return self.productseries |
274 | + |
275 | + @property |
276 | + def supports_code_imports(self): |
277 | + """See `IBranchTarget`.""" |
278 | + return False |
279 | + |
280 | + |
281 | def get_canonical_url_data_for_target(branch_target): |
282 | """Return the `ICanonicalUrlData` for an `IBranchTarget`.""" |
283 | return ICanonicalUrlData(branch_target.context) |
284 | |
285 | === modified file 'lib/lp/code/model/codeimport.py' |
286 | --- lib/lp/code/model/codeimport.py 2010-03-26 01:41:16 +0000 |
287 | +++ lib/lp/code/model/codeimport.py 2010-04-13 15:21:36 +0000 |
288 | @@ -203,7 +203,8 @@ |
289 | implements(ICodeImportSet) |
290 | |
291 | def new(self, registrant, target, branch_name, rcs_type, |
292 | - url=None, cvs_root=None, cvs_module=None, review_status=None): |
293 | + url=None, cvs_root=None, cvs_module=None, review_status=None, |
294 | + owner=None): |
295 | """See `ICodeImportSet`.""" |
296 | if rcs_type == RevisionControlSystems.CVS: |
297 | assert cvs_root is not None and cvs_module is not None |
298 | @@ -227,14 +228,16 @@ |
299 | review_status = CodeImportReviewStatus.NEW |
300 | if not target.supports_code_imports: |
301 | raise AssertionError("%r doesn't support code imports" % target) |
302 | + if owner is None: |
303 | + owner = registrant |
304 | # Create the branch for the CodeImport. |
305 | - namespace = target.getNamespace(registrant) |
306 | + namespace = target.getNamespace(owner) |
307 | import_branch = namespace.createBranch( |
308 | branch_type=BranchType.IMPORTED, name=branch_name, |
309 | registrant=registrant) |
310 | |
311 | code_import = CodeImport( |
312 | - registrant=registrant, owner=registrant, branch=import_branch, |
313 | + registrant=registrant, owner=owner, branch=import_branch, |
314 | rcs_type=rcs_type, url=url, |
315 | cvs_root=cvs_root, cvs_module=cvs_module, |
316 | review_status=review_status) |
317 | |
318 | === modified file 'lib/lp/code/model/hasbranches.py' |
319 | --- lib/lp/code/model/hasbranches.py 2010-03-09 17:30:18 +0000 |
320 | +++ lib/lp/code/model/hasbranches.py 2010-04-13 15:21:36 +0000 |
321 | @@ -6,6 +6,7 @@ |
322 | __metaclass__ = type |
323 | __all__ = [ |
324 | 'HasBranchesMixin', |
325 | + 'HasCodeImportsMixin', |
326 | 'HasMergeProposalsMixin', |
327 | 'HasRequestedReviewsMixin', |
328 | ] |
329 | @@ -16,6 +17,7 @@ |
330 | from lp.code.interfaces.branch import DEFAULT_BRANCH_STATUS_IN_LISTING |
331 | from lp.code.interfaces.branchcollection import ( |
332 | IAllBranches, IBranchCollection) |
333 | +from lp.code.interfaces.branchtarget import IBranchTarget |
334 | |
335 | |
336 | class HasBranchesMixin: |
337 | @@ -59,5 +61,17 @@ |
338 | |
339 | visible_branches = getUtility(IAllBranches).visibleByUser( |
340 | visible_by_user) |
341 | - proposals = visible_branches.getMergeProposalsForReviewer(self, status) |
342 | + proposals = visible_branches.getMergeProposalsForReviewer( |
343 | + self, status) |
344 | return proposals |
345 | + |
346 | + |
347 | +class HasCodeImportsMixin: |
348 | + |
349 | + def newCodeImport(self, registrant=None, branch_name=None, |
350 | + rcs_type=None, url=None, cvs_root=None, cvs_module=None, |
351 | + owner=None): |
352 | + """See `IHasCodeImports`.""" |
353 | + return IBranchTarget(self).newCodeImport(registrant, branch_name, |
354 | + rcs_type, url=url, cvs_root=cvs_root, cvs_module=cvs_module, |
355 | + owner=owner) |
356 | |
357 | === modified file 'lib/lp/code/model/tests/test_codeimport.py' |
358 | --- lib/lp/code/model/tests/test_codeimport.py 2010-04-13 15:21:34 +0000 |
359 | +++ lib/lp/code/model/tests/test_codeimport.py 2010-04-13 15:21:36 +0000 |
360 | @@ -16,6 +16,7 @@ |
361 | from lp.code.model.codeimportevent import CodeImportEvent |
362 | from lp.code.model.codeimportjob import CodeImportJob, CodeImportJobSet |
363 | from lp.code.model.codeimportresult import CodeImportResult |
364 | +from lp.code.interfaces.branch import BranchCreatorNotMemberOfOwnerTeam |
365 | from lp.code.interfaces.branchtarget import IBranchTarget |
366 | from lp.registry.interfaces.person import IPersonSet |
367 | from lp.code.enums import ( |
368 | @@ -155,6 +156,45 @@ |
369 | # And a job is still created |
370 | self.assertIsNot(None, code_import.import_job) |
371 | |
372 | + def test_set_owner(self): |
373 | + """Test that we can create an import owned by someone else.""" |
374 | + registrant = self.factory.makePerson() |
375 | + owner = self.factory.makeTeam() |
376 | + removeSecurityProxy(registrant).join(owner) |
377 | + source_package = self.factory.makeSourcePackage() |
378 | + target = IBranchTarget(source_package) |
379 | + code_import = CodeImportSet().new( |
380 | + registrant=registrant, |
381 | + target=target, |
382 | + branch_name='imported', |
383 | + rcs_type=RevisionControlSystems.HG, |
384 | + url=self.factory.getUniqueURL(), |
385 | + review_status=None, owner=owner) |
386 | + code_import = removeSecurityProxy(code_import) |
387 | + self.assertEqual(registrant, code_import.registrant) |
388 | + self.assertEqual(owner, code_import.branch.owner) |
389 | + self.assertEqual(registrant, code_import.branch.registrant) |
390 | + self.assertEqual(target, code_import.branch.target) |
391 | + self.assertEqual(source_package, code_import.branch.sourcepackage) |
392 | + # And a job is still created |
393 | + self.assertIsNot(None, code_import.import_job) |
394 | + |
395 | + def test_registrant_must_be_in_owner(self): |
396 | + """Test that we can't create an import for an arbitrary team.""" |
397 | + registrant = self.factory.makePerson() |
398 | + owner = self.factory.makeTeam() |
399 | + source_package = self.factory.makeSourcePackage() |
400 | + target = IBranchTarget(source_package) |
401 | + self.assertRaises( |
402 | + BranchCreatorNotMemberOfOwnerTeam, |
403 | + CodeImportSet().new, |
404 | + registrant=registrant, |
405 | + target=target, |
406 | + branch_name='imported', |
407 | + rcs_type=RevisionControlSystems.HG, |
408 | + url=self.factory.getUniqueURL(), |
409 | + review_status=None, owner=owner) |
410 | + |
411 | |
412 | class TestCodeImportDeletion(TestCaseWithFactory): |
413 | """Test the deletion of CodeImports.""" |
414 | |
415 | === modified file 'lib/lp/code/stories/webservice/xx-branchmergeproposal.txt' |
416 | --- lib/lp/code/stories/webservice/xx-branchmergeproposal.txt 2010-04-12 15:53:56 +0000 |
417 | +++ lib/lp/code/stories/webservice/xx-branchmergeproposal.txt 2010-04-13 15:21:36 +0000 |
418 | @@ -84,7 +84,7 @@ |
419 | ... initial_comment='Merge\nit!', needs_review=True, |
420 | ... commit_message='It was merged!\n', reviewers=[reviewer_url], |
421 | ... review_types=['green']) |
422 | - HTTP/1.1 500 Internal Server Error ... |
423 | + HTTP/1.1 400 Bad Request ... |
424 | BranchMergeProposalExists: There is already a branch merge proposal |
425 | registered for branch ... to land on ... that is still active. |
426 | |
427 | |
428 | === modified file 'lib/lp/code/stories/webservice/xx-code-import.txt' |
429 | --- lib/lp/code/stories/webservice/xx-code-import.txt 2010-04-13 15:21:34 +0000 |
430 | +++ lib/lp/code/stories/webservice/xx-code-import.txt 2010-04-13 15:21:36 +0000 |
431 | @@ -5,24 +5,33 @@ |
432 | if it is an import branch. |
433 | |
434 | >>> from zope.security.proxy import removeSecurityProxy |
435 | + >>> from canonical.launchpad.testing.pages import webservice_for_person |
436 | + >>> from canonical.launchpad.webapp.interfaces import OAuthPermission |
437 | |
438 | First we create some objects for use in the tests. |
439 | |
440 | >>> login(ANONYMOUS) |
441 | >>> person = factory.makePerson(name='import-owner') |
442 | + >>> team = factory.makeTeam(name='import-owner-team') |
443 | + >>> other_team = factory.makeTeam(name='other-team') |
444 | + >>> other_person = factory.makePerson(name='other-person') |
445 | + >>> removeSecurityProxy(person).join(team) |
446 | >>> product = factory.makeProduct(name='scruff') |
447 | + >>> svn_branch_url = "http://svn.domain.com/source" |
448 | >>> code_import = removeSecurityProxy(factory.makeProductCodeImport( |
449 | ... registrant=person, product=product, branch_name='import', |
450 | - ... svn_branch_url="http://svn.domain.com/source")) |
451 | + ... svn_branch_url=svn_branch_url)) |
452 | >>> no_import_branch = removeSecurityProxy(factory.makeProductBranch( |
453 | ... owner=person, product=product, name='no-import')) |
454 | >>> logout() |
455 | + >>> import_webservice = webservice_for_person( |
456 | + ... person, permission=OAuthPermission.WRITE_PUBLIC) |
457 | |
458 | If we query a branch with no import then we find that it tells us |
459 | it doesn't have one. |
460 | |
461 | >>> branch_url = '/' + no_import_branch.unique_name |
462 | - >>> response = webservice.get(branch_url) |
463 | + >>> response = import_webservice.get(branch_url) |
464 | >>> representation = response.jsonBody() |
465 | >>> print representation['code_import_link'] |
466 | None |
467 | @@ -31,7 +40,7 @@ |
468 | representation. |
469 | |
470 | >>> branch_url = '/' + code_import.branch.unique_name |
471 | - >>> response = webservice.get(branch_url) |
472 | + >>> response = import_webservice.get(branch_url) |
473 | >>> representation = response.jsonBody() |
474 | >>> print representation['code_import_link'] |
475 | http://.../~import-owner/scruff/import/+code-import |
476 | @@ -39,7 +48,7 @@ |
477 | We can get some information about the import using this URL. |
478 | |
479 | >>> import_url = representation['code_import_link'] |
480 | - >>> response = webservice.get(import_url) |
481 | + >>> response = import_webservice.get(import_url) |
482 | >>> representation = response.jsonBody() |
483 | >>> print representation['self_link'] == import_url |
484 | True |
485 | @@ -69,17 +78,19 @@ |
486 | >>> distroseries = factory.makeDistroSeries( |
487 | ... name='manic', distribution=distribution) |
488 | >>> source_package = factory.makeSourcePackage( |
489 | - ... sourcename='scruff', distroseries=distroseries) |
490 | + ... sourcepackagename='scruff', distroseries=distroseries) |
491 | >>> code_import = removeSecurityProxy(factory.makePackageCodeImport( |
492 | ... registrant=person, sourcepackage=source_package, |
493 | ... branch_name='import', |
494 | ... svn_branch_url="http://svn.domain.com/package_source")) |
495 | >>> logout() |
496 | + >>> import_webservice = webservice_for_person( |
497 | + ... person, permission=OAuthPermission.WRITE_PUBLIC) |
498 | |
499 | There is a link on the branch object |
500 | |
501 | >>> branch_url = '/' + code_import.branch.unique_name |
502 | - >>> response = webservice.get(branch_url) |
503 | + >>> response = import_webservice.get(branch_url) |
504 | >>> representation = response.jsonBody() |
505 | >>> print representation['code_import_link'] |
506 | http://.../~import-owner/scruffbuntu/manic/scruff/import/+code-import |
507 | @@ -87,7 +98,7 @@ |
508 | and there is information available about the import itsef. |
509 | |
510 | >>> import_url = representation['code_import_link'] |
511 | - >>> response = webservice.get(import_url) |
512 | + >>> response = import_webservice.get(import_url) |
513 | >>> representation = response.jsonBody() |
514 | >>> print representation['self_link'] == import_url |
515 | True |
516 | @@ -106,3 +117,114 @@ |
517 | >>> print representation['date_last_successful'] |
518 | None |
519 | |
520 | +== Creating Imports == |
521 | + |
522 | +We can create an import using the API by calling a method on the project. |
523 | + |
524 | + >>> product_url = '/' + product.name |
525 | + >>> new_remote_url = factory.getUniqueURL() |
526 | + >>> response = import_webservice.named_post(product_url, 'newCodeImport', |
527 | + ... branch_name='new-import', rcs_type='Git', |
528 | + ... url=new_remote_url) |
529 | + >>> print response.status |
530 | + 201 |
531 | + >>> location = response.getHeader('Location') |
532 | + >>> response = import_webservice.get(location) |
533 | + >>> representation = response.jsonBody() |
534 | + >>> print representation['self_link'] |
535 | + http://.../~import-owner/scruff/new-import/+code-import |
536 | + >>> print representation['branch_link'] |
537 | + http://.../~import-owner/scruff/new-import |
538 | + >>> print representation['rcs_type'] |
539 | + Git |
540 | + >>> print representation['url'] == new_remote_url |
541 | + True |
542 | + >>> print representation['cvs_root'] |
543 | + None |
544 | + >>> print representation['cvs_module'] |
545 | + None |
546 | + >>> print representation['date_last_successful'] |
547 | + None |
548 | + |
549 | +If we must we can create a CVS import. |
550 | + |
551 | + >>> product_url = '/' + product.name |
552 | + >>> new_remote_url = factory.getUniqueURL() |
553 | + >>> response = import_webservice.named_post(product_url, 'newCodeImport', |
554 | + ... branch_name='cvs-import', rcs_type='Concurrent Versions System', |
555 | + ... cvs_root=new_remote_url, cvs_module="foo") |
556 | + >>> print response.status |
557 | + 201 |
558 | + >>> location = response.getHeader('Location') |
559 | + >>> response = import_webservice.get(location) |
560 | + >>> representation = response.jsonBody() |
561 | + >>> print representation['self_link'] |
562 | + http://.../~import-owner/scruff/cvs-import/+code-import |
563 | + >>> print representation['branch_link'] |
564 | + http://.../~import-owner/scruff/cvs-import |
565 | + >>> print representation['rcs_type'] |
566 | + Concurrent Versions System |
567 | + >>> print representation['url'] |
568 | + None |
569 | + >>> print representation['cvs_root'] == new_remote_url |
570 | + True |
571 | + >>> print representation['cvs_module'] == "foo" |
572 | + True |
573 | + >>> print representation['date_last_successful'] |
574 | + None |
575 | + |
576 | +We can also create an import targetting a source package. |
577 | + |
578 | + >>> source_package_url = ( |
579 | + ... '/' + distribution.name + '/' + distroseries.name + '/+source/' |
580 | + ... + source_package.name) |
581 | + >>> new_remote_url = factory.getUniqueURL() |
582 | + >>> response = import_webservice.named_post(source_package_url, |
583 | + ... 'newCodeImport', branch_name='new-import', rcs_type='Mercurial', |
584 | + ... url=new_remote_url) |
585 | + >>> print response.status |
586 | + 201 |
587 | + >>> location = response.getHeader('Location') |
588 | + >>> response = import_webservice.get(location) |
589 | + >>> representation = response.jsonBody() |
590 | + >>> print representation['self_link'] |
591 | + http://.../~import-owner/scruffbuntu/manic/scruff/new-import/+code-import |
592 | + >>> print representation['branch_link'] |
593 | + http://.../~import-owner/scruffbuntu/manic/scruff/new-import |
594 | + >>> print representation['rcs_type'] |
595 | + Mercurial |
596 | + >>> print representation['url'] == new_remote_url |
597 | + True |
598 | + >>> print representation['cvs_root'] |
599 | + None |
600 | + >>> print representation['cvs_module'] |
601 | + None |
602 | + >>> print representation['date_last_successful'] |
603 | + None |
604 | + |
605 | +If we wish to create a branch owned by a team we are part of then we can. |
606 | + |
607 | + >>> team_url = import_webservice.getAbsoluteUrl('/~import-owner-team') |
608 | + >>> new_remote_url = factory.getUniqueURL() |
609 | + >>> response = import_webservice.named_post(product_url, 'newCodeImport', |
610 | + ... branch_name='team-import', rcs_type='Git', |
611 | + ... url=new_remote_url, owner=team_url) |
612 | + >>> print response.status |
613 | + 201 |
614 | + >>> location = response.getHeader('Location') |
615 | + >>> response = import_webservice.get(location) |
616 | + >>> representation = response.jsonBody() |
617 | + >>> print representation['self_link'] |
618 | + http://.../~import-owner-team/scruff/team-import/+code-import |
619 | + >>> print representation['branch_link'] |
620 | + http://.../~import-owner-team/scruff/team-import |
621 | + >>> print representation['rcs_type'] |
622 | + Git |
623 | + >>> print representation['url'] == new_remote_url |
624 | + True |
625 | + >>> print representation['cvs_root'] |
626 | + None |
627 | + >>> print representation['cvs_module'] |
628 | + None |
629 | + >>> print representation['date_last_successful'] |
630 | + None |
631 | |
632 | === modified file 'lib/lp/registry/interfaces/product.py' |
633 | --- lib/lp/registry/interfaces/product.py 2010-04-07 19:23:28 +0000 |
634 | +++ lib/lp/registry/interfaces/product.py 2010-04-13 15:21:36 +0000 |
635 | @@ -43,7 +43,8 @@ |
636 | from lp.app.interfaces.headings import IRootContext |
637 | from lp.code.interfaces.branchvisibilitypolicy import ( |
638 | IHasBranchVisibilityPolicy) |
639 | -from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals |
640 | +from lp.code.interfaces.hasbranches import ( |
641 | + IHasBranches, IHasCodeImports, IHasMergeProposals) |
642 | from lp.code.interfaces.hasrecipes import IHasRecipes |
643 | from lp.bugs.interfaces.bugtarget import ( |
644 | IBugTarget, IOfficialBugTagTargetPublic, IOfficialBugTagTargetRestricted) |
645 | @@ -339,7 +340,8 @@ |
646 | IHasLogo, IHasMentoringOffers, IHasMergeProposals, IHasMilestones, |
647 | IHasMugshot, IHasOwner, IHasSecurityContact, IHasSprints, |
648 | ITranslationPolicy, IKarmaContext, ILaunchpadUsage, IMakesAnnouncements, |
649 | - IOfficialBugTagTargetPublic, IPillar, ISpecificationTarget, IHasRecipes): |
650 | + IOfficialBugTagTargetPublic, IPillar, ISpecificationTarget, IHasRecipes, |
651 | + IHasCodeImports): |
652 | """Public IProduct properties.""" |
653 | |
654 | # XXX Mark Shuttleworth 2004-10-12: Let's get rid of ID's in interfaces |
655 | |
656 | === modified file 'lib/lp/registry/interfaces/sourcepackage.py' |
657 | --- lib/lp/registry/interfaces/sourcepackage.py 2010-03-02 15:30:18 +0000 |
658 | +++ lib/lp/registry/interfaces/sourcepackage.py 2010-04-13 15:21:36 +0000 |
659 | @@ -22,7 +22,8 @@ |
660 | |
661 | from canonical.launchpad import _ |
662 | from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags |
663 | -from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals |
664 | +from lp.code.interfaces.hasbranches import ( |
665 | + IHasBranches, IHasCodeImports, IHasMergeProposals) |
666 | from lp.soyuz.interfaces.component import IComponent |
667 | from lazr.restful.fields import Reference, ReferenceChoice |
668 | from lazr.restful.declarations import ( |
669 | @@ -32,7 +33,7 @@ |
670 | |
671 | |
672 | class ISourcePackage(IBugTarget, IHasBranches, IHasMergeProposals, |
673 | - IHasOfficialBugTags): |
674 | + IHasOfficialBugTags, IHasCodeImports): |
675 | """A SourcePackage. See the MagicSourcePackage specification. This |
676 | interface preserves as much as possible of the old SourcePackage |
677 | interface from the SourcePackage table, with the new table-less |
678 | |
679 | === modified file 'lib/lp/registry/model/product.py' |
680 | --- lib/lp/registry/model/product.py 2010-04-09 19:11:36 +0000 |
681 | +++ lib/lp/registry/model/product.py 2010-04-13 15:21:36 +0000 |
682 | @@ -33,7 +33,8 @@ |
683 | from canonical.launchpad.interfaces.lpstorm import IStore |
684 | from lp.code.model.branchvisibilitypolicy import ( |
685 | BranchVisibilityPolicyMixin) |
686 | -from lp.code.model.hasbranches import HasBranchesMixin, HasMergeProposalsMixin |
687 | +from lp.code.model.hasbranches import ( |
688 | + HasBranchesMixin, HasCodeImportsMixin, HasMergeProposalsMixin) |
689 | from lp.code.model.sourcepackagerecipe import SourcePackageRecipe |
690 | from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData |
691 | from lp.bugs.interfaces.bugtarget import IHasBugHeat |
692 | @@ -173,8 +174,7 @@ |
693 | HasAliasMixin, StructuralSubscriptionTargetMixin, |
694 | HasMilestonesMixin, OfficialBugTagTargetMixin, HasBranchesMixin, |
695 | HasCustomLanguageCodesMixin, HasMergeProposalsMixin, |
696 | - HasBugHeatMixin): |
697 | - |
698 | + HasBugHeatMixin, HasCodeImportsMixin): |
699 | """A Product.""" |
700 | |
701 | implements( |
702 | |
703 | === modified file 'lib/lp/registry/model/sourcepackage.py' |
704 | --- lib/lp/registry/model/sourcepackage.py 2010-04-12 08:29:02 +0000 |
705 | +++ lib/lp/registry/model/sourcepackage.py 2010-04-13 15:21:36 +0000 |
706 | @@ -23,7 +23,8 @@ |
707 | from canonical.lazr.utils import smartquote |
708 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
709 | from lp.code.model.branch import Branch |
710 | -from lp.code.model.hasbranches import HasBranchesMixin, HasMergeProposalsMixin |
711 | +from lp.code.model.hasbranches import ( |
712 | + HasBranchesMixin, HasCodeImportsMixin, HasMergeProposalsMixin) |
713 | from lp.bugs.interfaces.bugtarget import IHasBugHeat |
714 | from lp.bugs.model.bug import get_bug_tags_open_count |
715 | from lp.bugs.model.bugtarget import BugTargetBase, HasBugHeatMixin |
716 | @@ -160,7 +161,7 @@ |
717 | class SourcePackage(BugTargetBase, SourcePackageQuestionTargetMixin, |
718 | HasTranslationImportsMixin, HasTranslationTemplatesMixin, |
719 | HasBranchesMixin, HasMergeProposalsMixin, |
720 | - HasBugHeatMixin): |
721 | + HasBugHeatMixin, HasCodeImportsMixin): |
722 | """A source package, e.g. apache2, in a distroseries. |
723 | |
724 | This object is not a true database object, but rather attempts to |
725 | @@ -681,7 +682,6 @@ |
726 | our_format = PackageUploadCustomFormat.ROSETTA_TRANSLATIONS |
727 | |
728 | packagename = self.sourcepackagename.name |
729 | - displayname = self.displayname |
730 | distro = self.distroseries.distribution |
731 | |
732 | histories = distro.main_archive.getPublishedSources( |
Passes ec2, so requesting a review.
Thanks,
James