Merge lp:~cjwatson/launchpad/codeimport-git-model into lp:launchpad
- codeimport-git-model
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18223 |
Proposed branch: | lp:~cjwatson/launchpad/codeimport-git-model |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/git-repository-type |
Diff against target: |
1495 lines (+427/-159) 16 files modified
lib/lp/code/configure.zcml (+2/-0) lib/lp/code/doc/codeimport.txt (+14/-4) lib/lp/code/emailtemplates/code-import-status-updated.txt (+1/-1) lib/lp/code/emailtemplates/new-code-import.txt (+1/-1) lib/lp/code/enums.py (+21/-1) lib/lp/code/interfaces/codeimport.py (+16/-4) lib/lp/code/interfaces/gitnamespace.py (+8/-2) lib/lp/code/mail/codeimport.py (+11/-11) lib/lp/code/mail/tests/test_codeimport.py (+35/-8) lib/lp/code/model/codeimport.py (+69/-18) lib/lp/code/model/codeimportjob.py (+14/-13) lib/lp/code/model/gitnamespace.py (+4/-1) lib/lp/code/model/hasbranches.py (+6/-2) lib/lp/code/model/tests/test_codeimport.py (+200/-85) lib/lp/registry/browser/product.py (+5/-1) lib/lp/testing/factory.py (+20/-7) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/codeimport-git-model |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant (community) | code | Approve | |
Review via email:
|
Commit message
Add basic model for code imports that target Git.
Description of the change
Add basic model for code imports that target Git.
This is overlong, but since I had to introduce the concept of a different target revision control system type here, it was tough to make it any shorter. ICodeImport.
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Colin Watson (cjwatson) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/code/configure.zcml' |
2 | --- lib/lp/code/configure.zcml 2016-06-20 20:32:36 +0000 |
3 | +++ lib/lp/code/configure.zcml 2016-10-03 17:03:17 +0000 |
4 | @@ -633,6 +633,8 @@ |
5 | <allow attributes="id |
6 | date_created |
7 | branch |
8 | + git_repository |
9 | + target |
10 | registrant |
11 | owner |
12 | assignee |
13 | |
14 | === modified file 'lib/lp/code/doc/codeimport.txt' |
15 | --- lib/lp/code/doc/codeimport.txt 2016-10-03 17:03:12 +0000 |
16 | +++ lib/lp/code/doc/codeimport.txt 2016-10-03 17:03:17 +0000 |
17 | @@ -52,8 +52,12 @@ |
18 | |
19 | The rcs_type field, which indicates whether the import is from CVS or |
20 | Subversion, takes values from the 'RevisionControlSystems' vocabulary. |
21 | +Similarly, target_rcs_type takes values from 'TargetRevisionControlSystems'. |
22 | |
23 | - >>> from lp.code.enums import RevisionControlSystems |
24 | + >>> from lp.code.enums import ( |
25 | + ... RevisionControlSystems, |
26 | + ... TargetRevisionControlSystems, |
27 | + ... ) |
28 | >>> for item in RevisionControlSystems: |
29 | ... print item.title |
30 | Concurrent Versions System |
31 | @@ -62,6 +66,10 @@ |
32 | Git |
33 | Mercurial |
34 | Bazaar |
35 | + >>> for item in TargetRevisionControlSystems: |
36 | + ... print item.title |
37 | + Bazaar |
38 | + Git |
39 | |
40 | |
41 | Import from CVS |
42 | @@ -71,12 +79,14 @@ |
43 | in the repository, known as the "module". |
44 | |
45 | >>> cvs = RevisionControlSystems.CVS |
46 | + >>> target_bzr = TargetRevisionControlSystems.BZR |
47 | >>> cvs_root = ':pserver:anonymous@cvs.example.com:/cvsroot' |
48 | >>> cvs_module = 'hello' |
49 | >>> context = factory.makeProduct(name='widget') |
50 | >>> cvs_import = code_import_set.new( |
51 | ... registrant=nopriv, context=context, branch_name='trunk-cvs', |
52 | - ... rcs_type=cvs, cvs_root=cvs_root, cvs_module=cvs_module) |
53 | + ... rcs_type=cvs, cvs_root=cvs_root, cvs_module=cvs_module, |
54 | + ... target_rcs_type=target_bzr) |
55 | >>> verifyObject(ICodeImport, removeSecurityProxy(cvs_import)) |
56 | True |
57 | |
58 | @@ -136,7 +146,7 @@ |
59 | >>> svn_url = 'svn://svn.example.com/trunk' |
60 | >>> svn_import = code_import_set.new( |
61 | ... registrant=nopriv, context=context, branch_name='trunk-svn', |
62 | - ... rcs_type=svn, url=svn_url) |
63 | + ... rcs_type=svn, url=svn_url, target_rcs_type=target_bzr) |
64 | >>> verifyObject(ICodeImport, removeSecurityProxy(svn_import)) |
65 | True |
66 | |
67 | @@ -164,7 +174,7 @@ |
68 | >>> git_url = 'git://git.example.com/hello.git' |
69 | >>> git_import = code_import_set.new( |
70 | ... registrant=nopriv, context=context, branch_name='trunk-git', |
71 | - ... rcs_type=git, url=git_url) |
72 | + ... rcs_type=git, url=git_url, target_rcs_type=target_bzr) |
73 | >>> verifyObject(ICodeImport, removeSecurityProxy(git_import)) |
74 | True |
75 | |
76 | |
77 | === modified file 'lib/lp/code/emailtemplates/code-import-status-updated.txt' |
78 | --- lib/lp/code/emailtemplates/code-import-status-updated.txt 2011-12-18 22:31:46 +0000 |
79 | +++ lib/lp/code/emailtemplates/code-import-status-updated.txt 2016-10-03 17:03:17 +0000 |
80 | @@ -3,5 +3,5 @@ |
81 | %(body)s |
82 | |
83 | -- |
84 | -%(branch)s |
85 | +%(target)s |
86 | %(rationale)s%(unsubscribe)s |
87 | |
88 | === modified file 'lib/lp/code/emailtemplates/new-code-import.txt' |
89 | --- lib/lp/code/emailtemplates/new-code-import.txt 2011-12-18 22:31:46 +0000 |
90 | +++ lib/lp/code/emailtemplates/new-code-import.txt 2016-10-03 17:03:17 +0000 |
91 | @@ -1,5 +1,5 @@ |
92 | A new %(rcs_type)s code import has been requested by %(person)s: |
93 | - %(branch)s |
94 | + %(target)s |
95 | from |
96 | %(location)s |
97 | |
98 | |
99 | === modified file 'lib/lp/code/enums.py' |
100 | --- lib/lp/code/enums.py 2016-10-03 17:03:12 +0000 |
101 | +++ lib/lp/code/enums.py 2016-10-03 17:03:17 +0000 |
102 | @@ -1,4 +1,4 @@ |
103 | -# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
104 | +# Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
105 | # GNU Affero General Public License version 3 (see the file LICENSE). |
106 | |
107 | """Enumerations used in the lp/code modules.""" |
108 | @@ -24,6 +24,7 @@ |
109 | 'GitRepositoryType', |
110 | 'NON_CVS_RCS_TYPES', |
111 | 'RevisionControlSystems', |
112 | + 'TargetRevisionControlSystems', |
113 | ] |
114 | |
115 | from lazr.enum import ( |
116 | @@ -400,6 +401,25 @@ |
117 | """) |
118 | |
119 | |
120 | +class TargetRevisionControlSystems(EnumeratedType): |
121 | + """Target Revision Control Systems |
122 | + |
123 | + Revision control systems that can be the target of a code import. |
124 | + """ |
125 | + |
126 | + BZR = Item(""" |
127 | + Bazaar |
128 | + |
129 | + Import to Bazaar. |
130 | + """) |
131 | + |
132 | + GIT = Item(""" |
133 | + Git |
134 | + |
135 | + Import to Git. |
136 | + """) |
137 | + |
138 | + |
139 | class CodeImportReviewStatus(DBEnumeratedType): |
140 | """CodeImport review status. |
141 | |
142 | |
143 | === modified file 'lib/lp/code/interfaces/codeimport.py' |
144 | --- lib/lp/code/interfaces/codeimport.py 2016-09-30 13:27:27 +0000 |
145 | +++ lib/lp/code/interfaces/codeimport.py 2016-10-03 17:03:17 +0000 |
146 | @@ -43,6 +43,7 @@ |
147 | RevisionControlSystems, |
148 | ) |
149 | from lp.code.interfaces.branch import IBranch |
150 | +from lp.code.interfaces.gitrepository import IGitRepository |
151 | from lp.services.fields import ( |
152 | PublicPersonChoice, |
153 | URIField, |
154 | @@ -86,10 +87,18 @@ |
155 | |
156 | branch = exported( |
157 | ReferenceChoice( |
158 | - title=_('Branch'), required=True, readonly=True, |
159 | + title=_('Branch'), required=False, readonly=True, |
160 | vocabulary='Branch', schema=IBranch, |
161 | description=_("The Bazaar branch produced by the " |
162 | "import system."))) |
163 | + git_repository = exported( |
164 | + ReferenceChoice( |
165 | + title=_('Git repository'), required=False, readonly=True, |
166 | + vocabulary='GitRepository', schema=IGitRepository, |
167 | + description=_( |
168 | + "The Git repository produced by the import system."))) |
169 | + target = Attribute( |
170 | + "The branch/repository produced by the import system (VCS-agnostic).") |
171 | |
172 | registrant = PublicPersonChoice( |
173 | title=_('Registrant'), required=True, readonly=True, |
174 | @@ -220,8 +229,8 @@ |
175 | class ICodeImportSet(Interface): |
176 | """Interface representing the set of code imports.""" |
177 | |
178 | - def new(registrant, context, branch_name, rcs_type, url=None, |
179 | - cvs_root=None, cvs_module=None, review_status=None, |
180 | + def new(registrant, context, branch_name, rcs_type, target_rcs_type, |
181 | + url=None, cvs_root=None, cvs_module=None, review_status=None, |
182 | owner=None): |
183 | """Create a new CodeImport. |
184 | |
185 | @@ -238,7 +247,10 @@ |
186 | """ |
187 | |
188 | def getByBranch(branch): |
189 | - """Get the CodeImport, if any, associated to a Branch.""" |
190 | + """Get the CodeImport, if any, associated with a Branch.""" |
191 | + |
192 | + def getByGitRepository(repository): |
193 | + """Get the CodeImport, if any, associated with a GitRepository.""" |
194 | |
195 | def getByCVSDetails(cvs_root, cvs_module): |
196 | """Get the CodeImport with the specified CVS details.""" |
197 | |
198 | === modified file 'lib/lp/code/interfaces/gitnamespace.py' |
199 | --- lib/lp/code/interfaces/gitnamespace.py 2016-10-03 17:03:12 +0000 |
200 | +++ lib/lp/code/interfaces/gitnamespace.py 2016-10-03 17:03:17 +0000 |
201 | @@ -1,4 +1,4 @@ |
202 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
203 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
204 | # GNU Affero General Public License version 3 (see the file LICENSE). |
205 | |
206 | """Interface for a Git repository namespace.""" |
207 | @@ -22,6 +22,7 @@ |
208 | from lp.registry.interfaces.distributionsourcepackage import ( |
209 | IDistributionSourcePackage, |
210 | ) |
211 | +from lp.registry.interfaces.person import IPerson |
212 | from lp.registry.interfaces.product import IProduct |
213 | |
214 | |
215 | @@ -96,6 +97,9 @@ |
216 | supports_merge_proposals = Attribute( |
217 | "Does this namespace support merge proposals at all?") |
218 | |
219 | + supports_code_imports = Attribute( |
220 | + "Does this namespace support code imports at all?") |
221 | + |
222 | allow_recipe_name_from_target = Attribute( |
223 | "Can recipe names reasonably be generated from the target name " |
224 | "rather than the branch name?") |
225 | @@ -188,8 +192,10 @@ |
226 | return getUtility(IGitNamespaceSet).get( |
227 | owner, distribution=target.distribution, |
228 | sourcepackagename=target.sourcepackagename) |
229 | + elif IPerson.providedBy(target): |
230 | + return getUtility(IGitNamespaceSet).get(owner) |
231 | else: |
232 | - return getUtility(IGitNamespaceSet).get(owner) |
233 | + raise AssertionError("No Git namespace defined for %s" % target) |
234 | |
235 | |
236 | # Marker for references to Git URL layouts: ##GITNAMESPACE## |
237 | |
238 | === modified file 'lib/lp/code/mail/codeimport.py' |
239 | --- lib/lp/code/mail/codeimport.py 2016-10-03 17:03:12 +0000 |
240 | +++ lib/lp/code/mail/codeimport.py 2016-10-03 17:03:17 +0000 |
241 | @@ -39,7 +39,7 @@ |
242 | # test. |
243 | return |
244 | user = IPerson(event.user) |
245 | - subject = 'New code import: %s' % code_import.branch.unique_name |
246 | + subject = 'New code import: %s' % code_import.target.unique_name |
247 | if code_import.rcs_type == RevisionControlSystems.CVS: |
248 | location = '%s, %s' % (code_import.cvs_root, code_import.cvs_module) |
249 | else: |
250 | @@ -52,7 +52,7 @@ |
251 | } |
252 | body = get_email_template('new-code-import.txt', app='code') % { |
253 | 'person': code_import.registrant.displayname, |
254 | - 'branch': canonical_url(code_import.branch), |
255 | + 'target': canonical_url(code_import.target), |
256 | 'rcs_type': rcs_type_map[code_import.rcs_type], |
257 | 'location': location, |
258 | } |
259 | @@ -61,7 +61,7 @@ |
260 | user.displayname, user.preferredemail.email) |
261 | |
262 | vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports |
263 | - headers = {'X-Launchpad-Branch': code_import.branch.unique_name, |
264 | + headers = {'X-Launchpad-Branch': code_import.target.unique_name, |
265 | 'X-Launchpad-Message-Rationale': |
266 | 'Operator @%s' % vcs_imports.name, |
267 | 'X-Launchpad-Message-For': vcs_imports.name, |
268 | @@ -103,7 +103,7 @@ |
269 | raise AssertionError('Unexpected review status for code import.') |
270 | |
271 | details_change_prefix = '\n'.join(textwrap.wrap( |
272 | - "%s is now being imported from:" % code_import.branch.unique_name)) |
273 | + "%s is now being imported from:" % code_import.target.unique_name)) |
274 | if code_import.rcs_type == RevisionControlSystems.CVS: |
275 | if (CodeImportEventDataType.OLD_CVS_ROOT in event_data or |
276 | CodeImportEventDataType.OLD_CVS_MODULE in event_data): |
277 | @@ -142,26 +142,26 @@ |
278 | |
279 | |
280 | def code_import_updated(code_import, event, new_whiteboard, person): |
281 | - """Email the branch subscribers, and the vcs-imports team with new status. |
282 | + """Email the target subscribers, and the vcs-imports team with new status. |
283 | """ |
284 | - branch = code_import.branch |
285 | - recipients = branch.getNotificationRecipients() |
286 | + target = code_import.target |
287 | + recipients = target.getNotificationRecipients() |
288 | # Add in the vcs-imports user. |
289 | vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports |
290 | herder_rationale = 'Operator @%s' % vcs_imports.name |
291 | recipients.add(vcs_imports, None, herder_rationale) |
292 | |
293 | - headers = {'X-Launchpad-Branch': branch.unique_name} |
294 | + headers = {'X-Launchpad-Branch': target.unique_name} |
295 | |
296 | subject = 'Code import %s status: %s' % ( |
297 | - code_import.branch.unique_name, code_import.review_status.title) |
298 | + code_import.target.unique_name, code_import.review_status.title) |
299 | |
300 | email_template = get_email_template( |
301 | 'code-import-status-updated.txt', app='code') |
302 | template_params = { |
303 | 'body': make_email_body_for_code_import_update( |
304 | code_import, event, new_whiteboard), |
305 | - 'branch': canonical_url(code_import.branch)} |
306 | + 'target': canonical_url(code_import.target)} |
307 | |
308 | if person: |
309 | from_address = format_address( |
310 | @@ -194,7 +194,7 @@ |
311 | # Give the users a link to unsubscribe. |
312 | template_params['unsubscribe'] = ( |
313 | "\nTo unsubscribe from this branch go to " |
314 | - "%s/+edit-subscription." % canonical_url(branch)) |
315 | + "%s/+edit-subscription." % canonical_url(target)) |
316 | else: |
317 | template_params['unsubscribe'] = '' |
318 | for_person = subscription.person |
319 | |
320 | === modified file 'lib/lp/code/mail/tests/test_codeimport.py' |
321 | --- lib/lp/code/mail/tests/test_codeimport.py 2014-06-10 16:13:03 +0000 |
322 | +++ lib/lp/code/mail/tests/test_codeimport.py 2016-10-03 17:03:17 +0000 |
323 | @@ -1,4 +1,4 @@ |
324 | -# Copyright 2010-2014 Canonical Ltd. This software is licensed under the |
325 | +# Copyright 2010-2016 Canonical Ltd. This software is licensed under the |
326 | # GNU Affero General Public License version 3 (see the file LICENSE). |
327 | |
328 | """Tests for code import related mailings""" |
329 | @@ -7,7 +7,10 @@ |
330 | |
331 | import transaction |
332 | |
333 | -from lp.code.enums import RevisionControlSystems |
334 | +from lp.code.enums import ( |
335 | + RevisionControlSystems, |
336 | + TargetRevisionControlSystems, |
337 | + ) |
338 | from lp.services.mail import stub |
339 | from lp.testing import ( |
340 | login_person, |
341 | @@ -21,8 +24,8 @@ |
342 | |
343 | layer = DatabaseFunctionalLayer |
344 | |
345 | - def test_cvs_import(self): |
346 | - # Test the email for a new CVS import. |
347 | + def test_cvs_to_bzr_import(self): |
348 | + # Test the email for a new CVS-to-Bazaar import. |
349 | eric = self.factory.makePerson(name='eric') |
350 | fooix = self.factory.makeProduct(name='fooix') |
351 | # Eric needs to be logged in for the mail to be sent. |
352 | @@ -44,8 +47,8 @@ |
353 | '-- \nYou are getting this email because you are a member of the ' |
354 | 'vcs-imports team.\n', msg.get_payload(decode=True)) |
355 | |
356 | - def test_svn_import(self): |
357 | - # Test the email for a new subversion import. |
358 | + def test_svn_to_bzr_import(self): |
359 | + # Test the email for a new Subversion-to-Bazaar import. |
360 | eric = self.factory.makePerson(name='eric') |
361 | fooix = self.factory.makeProduct(name='fooix') |
362 | # Eric needs to be logged in for the mail to be sent. |
363 | @@ -67,8 +70,8 @@ |
364 | '-- \nYou are getting this email because you are a member of the ' |
365 | 'vcs-imports team.\n', msg.get_payload(decode=True)) |
366 | |
367 | - def test_git_import(self): |
368 | - # Test the email for a new git import. |
369 | + def test_git_to_bzr_import(self): |
370 | + # Test the email for a new git-to-Bazaar import. |
371 | eric = self.factory.makePerson(name='eric') |
372 | fooix = self.factory.makeProduct(name='fooix') |
373 | # Eric needs to be logged in for the mail to be sent. |
374 | @@ -90,6 +93,30 @@ |
375 | '-- \nYou are getting this email because you are a member of the ' |
376 | 'vcs-imports team.\n', msg.get_payload(decode=True)) |
377 | |
378 | + def test_git_to_git_import(self): |
379 | + # Test the email for a new git-to-git import. |
380 | + eric = self.factory.makePerson(name='eric') |
381 | + fooix = self.factory.makeProduct(name='fooix') |
382 | + # Eric needs to be logged in for the mail to be sent. |
383 | + login_person(eric) |
384 | + self.factory.makeProductCodeImport( |
385 | + git_repo_url='git://git.example.com/fooix.git', |
386 | + branch_name=u'master', product=fooix, registrant=eric, |
387 | + target_rcs_type=TargetRevisionControlSystems.GIT) |
388 | + transaction.commit() |
389 | + msg = message_from_string(stub.test_emails[0][2]) |
390 | + self.assertEqual('code-import', msg['X-Launchpad-Notification-Type']) |
391 | + self.assertEqual('~eric/fooix/+git/master', msg['X-Launchpad-Branch']) |
392 | + self.assertEqual( |
393 | + 'A new git code import has been requested ' |
394 | + 'by Eric:\n' |
395 | + ' http://code.launchpad.dev/~eric/fooix/+git/master\n' |
396 | + 'from\n' |
397 | + ' git://git.example.com/fooix.git\n' |
398 | + '\n' |
399 | + '-- \nYou are getting this email because you are a member of the ' |
400 | + 'vcs-imports team.\n', msg.get_payload(decode=True)) |
401 | + |
402 | def test_new_source_package_import(self): |
403 | # Test the email for a new sourcepackage import. |
404 | eric = self.factory.makePerson(name='eric') |
405 | |
406 | === modified file 'lib/lp/code/model/codeimport.py' |
407 | --- lib/lp/code/model/codeimport.py 2016-09-30 13:27:27 +0000 |
408 | +++ lib/lp/code/model/codeimport.py 2016-10-03 17:03:17 +0000 |
409 | @@ -26,7 +26,10 @@ |
410 | Func, |
411 | Select, |
412 | ) |
413 | -from storm.locals import Store |
414 | +from storm.locals import ( |
415 | + Int, |
416 | + Store, |
417 | + ) |
418 | from storm.references import Reference |
419 | from zope.component import getUtility |
420 | from zope.event import notify |
421 | @@ -38,14 +41,17 @@ |
422 | CodeImportJobState, |
423 | CodeImportResultStatus, |
424 | CodeImportReviewStatus, |
425 | + GitRepositoryType, |
426 | NON_CVS_RCS_TYPES, |
427 | RevisionControlSystems, |
428 | + TargetRevisionControlSystems, |
429 | ) |
430 | from lp.code.errors import ( |
431 | CodeImportAlreadyRequested, |
432 | CodeImportAlreadyRunning, |
433 | CodeImportNotInReviewedState, |
434 | ) |
435 | +from lp.code.interfaces.branch import IBranch |
436 | from lp.code.interfaces.branchtarget import IBranchTarget |
437 | from lp.code.interfaces.codeimport import ( |
438 | ICodeImport, |
439 | @@ -53,6 +59,8 @@ |
440 | ) |
441 | from lp.code.interfaces.codeimportevent import ICodeImportEventSet |
442 | from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow |
443 | +from lp.code.interfaces.gitnamespace import get_git_namespace |
444 | +from lp.code.interfaces.gitrepository import IGitRepository |
445 | from lp.code.mail.codeimport import code_import_updated |
446 | from lp.code.model.codeimportjob import CodeImportJobWorkflow |
447 | from lp.code.model.codeimportresult import CodeImportResult |
448 | @@ -71,8 +79,31 @@ |
449 | _table = 'CodeImport' |
450 | _defaultOrder = ['id'] |
451 | |
452 | + def __init__(self, target=None, *args, **kwargs): |
453 | + if target is not None: |
454 | + assert 'branch' not in kwargs |
455 | + assert 'repository' not in kwargs |
456 | + if IBranch.providedBy(target): |
457 | + kwargs['branch'] = target |
458 | + elif IGitRepository.providedBy(target): |
459 | + kwargs['git_repository'] = target |
460 | + else: |
461 | + raise AssertionError("Unknown code import target %s" % target) |
462 | + super(CodeImport, self).__init__(*args, **kwargs) |
463 | + |
464 | date_created = UtcDateTimeCol(notNull=True, default=DEFAULT) |
465 | - branch = ForeignKey(dbName='branch', foreignKey='Branch', notNull=True) |
466 | + branch = ForeignKey(dbName='branch', foreignKey='Branch', notNull=False) |
467 | + git_repositoryID = Int(name='git_repository', allow_none=True) |
468 | + git_repository = Reference(git_repositoryID, 'GitRepository.id') |
469 | + |
470 | + @property |
471 | + def target(self): |
472 | + if self.branch is not None: |
473 | + return self.branch |
474 | + else: |
475 | + assert self.git_repository is not None |
476 | + return self.git_repository |
477 | + |
478 | registrant = ForeignKey( |
479 | dbName='registrant', foreignKey='Person', |
480 | storm_validator=validate_public_person, notNull=True) |
481 | @@ -175,12 +206,14 @@ |
482 | new_whiteboard = None |
483 | if 'whiteboard' in data: |
484 | whiteboard = data.pop('whiteboard') |
485 | - if whiteboard != self.branch.whiteboard: |
486 | - if whiteboard is None: |
487 | - new_whiteboard = '' |
488 | - else: |
489 | - new_whiteboard = whiteboard |
490 | - self.branch.whiteboard = whiteboard |
491 | + # XXX cjwatson 2016-10-03: Do we need something similar for Git? |
492 | + if self.branch is not None: |
493 | + if whiteboard != self.branch.whiteboard: |
494 | + if whiteboard is None: |
495 | + new_whiteboard = '' |
496 | + else: |
497 | + new_whiteboard = whiteboard |
498 | + self.branch.whiteboard = whiteboard |
499 | token = event_set.beginModify(self) |
500 | for name, value in data.items(): |
501 | setattr(self, name, value) |
502 | @@ -196,7 +229,7 @@ |
503 | return event |
504 | |
505 | def __repr__(self): |
506 | - return "<CodeImport for %s>" % self.branch.unique_name |
507 | + return "<CodeImport for %s>" % self.target.unique_name |
508 | |
509 | def tryFailingImportAgain(self, user): |
510 | """See `ICodeImport`.""" |
511 | @@ -233,7 +266,7 @@ |
512 | class CodeImportSet: |
513 | """See `ICodeImportSet`.""" |
514 | |
515 | - def new(self, registrant, context, branch_name, rcs_type, |
516 | + def new(self, registrant, context, branch_name, rcs_type, target_rcs_type, |
517 | url=None, cvs_root=None, cvs_module=None, review_status=None, |
518 | owner=None): |
519 | """See `ICodeImportSet`.""" |
520 | @@ -247,22 +280,37 @@ |
521 | raise AssertionError( |
522 | "Don't know how to sanity check source details for unknown " |
523 | "rcs_type %s" % rcs_type) |
524 | - target = IBranchTarget(context) |
525 | + if owner is None: |
526 | + owner = registrant |
527 | + if target_rcs_type == TargetRevisionControlSystems.BZR: |
528 | + target = IBranchTarget(context) |
529 | + namespace = target.getNamespace(owner) |
530 | + elif target_rcs_type == TargetRevisionControlSystems.GIT: |
531 | + if rcs_type != RevisionControlSystems.GIT: |
532 | + raise AssertionError( |
533 | + "Can't import rcs_type %s into a Git repository" % |
534 | + rcs_type) |
535 | + target = namespace = get_git_namespace(context, owner) |
536 | + else: |
537 | + raise AssertionError( |
538 | + "Can't import to target_rcs_type %s" % target_rcs_type) |
539 | if review_status is None: |
540 | # Auto approve imports. |
541 | review_status = CodeImportReviewStatus.REVIEWED |
542 | if not target.supports_code_imports: |
543 | raise AssertionError("%r doesn't support code imports" % target) |
544 | - if owner is None: |
545 | - owner = registrant |
546 | # Create the branch for the CodeImport. |
547 | - namespace = target.getNamespace(owner) |
548 | - import_branch = namespace.createBranch( |
549 | - branch_type=BranchType.IMPORTED, name=branch_name, |
550 | - registrant=registrant) |
551 | + if target_rcs_type == TargetRevisionControlSystems.BZR: |
552 | + import_target = namespace.createBranch( |
553 | + branch_type=BranchType.IMPORTED, name=branch_name, |
554 | + registrant=registrant) |
555 | + else: |
556 | + import_target = namespace.createRepository( |
557 | + repository_type=GitRepositoryType.IMPORTED, name=branch_name, |
558 | + registrant=registrant) |
559 | |
560 | code_import = CodeImport( |
561 | - registrant=registrant, owner=owner, branch=import_branch, |
562 | + registrant=registrant, owner=owner, target=import_target, |
563 | rcs_type=rcs_type, url=url, |
564 | cvs_root=cvs_root, cvs_module=cvs_module, |
565 | review_status=review_status) |
566 | @@ -303,6 +351,9 @@ |
567 | """See `ICodeImportSet`.""" |
568 | return CodeImport.selectOneBy(branch=branch) |
569 | |
570 | + def getByGitRepository(self, repository): |
571 | + return CodeImport.selectOneBy(git_repository=repository) |
572 | + |
573 | def search(self, review_status=None, rcs_type=None): |
574 | """See `ICodeImportSet`.""" |
575 | clauses = [] |
576 | |
577 | === modified file 'lib/lp/code/model/codeimportjob.py' |
578 | --- lib/lp/code/model/codeimportjob.py 2015-07-08 16:05:11 +0000 |
579 | +++ lib/lp/code/model/codeimportjob.py 2016-10-03 17:03:17 +0000 |
580 | @@ -1,4 +1,4 @@ |
581 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
582 | +# Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
583 | # GNU Affero General Public License version 3 (see the file LICENSE). |
584 | |
585 | """Database classes for the CodeImportJob table.""" |
586 | @@ -154,10 +154,10 @@ |
587 | """See `ICodeImportJobWorkflow`.""" |
588 | assert code_import.review_status == CodeImportReviewStatus.REVIEWED, ( |
589 | "Review status of %s is not REVIEWED: %s" % ( |
590 | - code_import.branch.unique_name, code_import.review_status.name)) |
591 | + code_import.target.unique_name, code_import.review_status.name)) |
592 | assert code_import.import_job is None, ( |
593 | "Already associated to a CodeImportJob: %s" % ( |
594 | - code_import.branch.unique_name)) |
595 | + code_import.target.unique_name)) |
596 | |
597 | if interval is None: |
598 | interval = code_import.effective_update_interval |
599 | @@ -182,13 +182,13 @@ |
600 | """See `ICodeImportJobWorkflow`.""" |
601 | assert code_import.review_status != CodeImportReviewStatus.REVIEWED, ( |
602 | "The review status of %s is %s." % ( |
603 | - code_import.branch.unique_name, code_import.review_status.name)) |
604 | + code_import.target.unique_name, code_import.review_status.name)) |
605 | assert code_import.import_job is not None, ( |
606 | "Not associated to a CodeImportJob: %s" % ( |
607 | - code_import.branch.unique_name,)) |
608 | + code_import.target.unique_name,)) |
609 | assert code_import.import_job.state == CodeImportJobState.PENDING, ( |
610 | "The CodeImportJob associated to %s is %s." % ( |
611 | - code_import.branch.unique_name, |
612 | + code_import.target.unique_name, |
613 | code_import.import_job.state.name)) |
614 | # CodeImportJobWorkflow is the only class that is allowed to delete |
615 | # CodeImportJob rows, so destroySelf is not exposed in ICodeImportJob. |
616 | @@ -198,12 +198,12 @@ |
617 | """See `ICodeImportJobWorkflow`.""" |
618 | assert import_job.state == CodeImportJobState.PENDING, ( |
619 | "The CodeImportJob associated with %s is %s." |
620 | - % (import_job.code_import.branch.unique_name, |
621 | + % (import_job.code_import.target.unique_name, |
622 | import_job.state.name)) |
623 | assert import_job.requesting_user is None, ( |
624 | "The CodeImportJob associated with %s " |
625 | "was already requested by %s." |
626 | - % (import_job.code_import.branch.unique_name, |
627 | + % (import_job.code_import.target.unique_name, |
628 | import_job.requesting_user.name)) |
629 | # CodeImportJobWorkflow is the only class that is allowed to set the |
630 | # date_due and requesting_user attributes of CodeImportJob, they are |
631 | @@ -219,7 +219,7 @@ |
632 | """See `ICodeImportJobWorkflow`.""" |
633 | assert import_job.state == CodeImportJobState.PENDING, ( |
634 | "The CodeImportJob associated with %s is %s." |
635 | - % (import_job.code_import.branch.unique_name, |
636 | + % (import_job.code_import.target.unique_name, |
637 | import_job.state.name)) |
638 | assert machine.state == CodeImportMachineState.ONLINE, ( |
639 | "The machine %s is %s." |
640 | @@ -241,7 +241,7 @@ |
641 | """See `ICodeImportJobWorkflow`.""" |
642 | assert import_job.state == CodeImportJobState.RUNNING, ( |
643 | "The CodeImportJob associated with %s is %s." |
644 | - % (import_job.code_import.branch.unique_name, |
645 | + % (import_job.code_import.target.unique_name, |
646 | import_job.state.name)) |
647 | # CodeImportJobWorkflow is the only class that is allowed to |
648 | # set the heartbeat and logtail attributes of CodeImportJob, |
649 | @@ -281,7 +281,7 @@ |
650 | """See `ICodeImportJobWorkflow`.""" |
651 | assert import_job.state == CodeImportJobState.RUNNING, ( |
652 | "The CodeImportJob associated with %s is %s." |
653 | - % (import_job.code_import.branch.unique_name, |
654 | + % (import_job.code_import.target.unique_name, |
655 | import_job.state.name)) |
656 | code_import = import_job.code_import |
657 | machine = import_job.machine |
658 | @@ -311,7 +311,8 @@ |
659 | naked_import.date_last_successful = result.date_created |
660 | # If the status was successful and revisions were imported, arrange |
661 | # for the branch to be mirrored. |
662 | - if status == CodeImportResultStatus.SUCCESS: |
663 | + if (status == CodeImportResultStatus.SUCCESS and |
664 | + code_import.branch is not None): |
665 | code_import.branch.requestMirror() |
666 | getUtility(ICodeImportEventSet).newFinish( |
667 | code_import, machine) |
668 | @@ -320,7 +321,7 @@ |
669 | """See `ICodeImportJobWorkflow`.""" |
670 | assert import_job.state == CodeImportJobState.RUNNING, ( |
671 | "The CodeImportJob associated with %s is %s." |
672 | - % (import_job.code_import.branch.unique_name, |
673 | + % (import_job.code_import.target.unique_name, |
674 | import_job.state.name)) |
675 | # Cribbing from codeimport-job.txt, this method does four things: |
676 | # 1) deletes the passed in job, |
677 | |
678 | === modified file 'lib/lp/code/model/gitnamespace.py' |
679 | --- lib/lp/code/model/gitnamespace.py 2016-10-03 17:03:12 +0000 |
680 | +++ lib/lp/code/model/gitnamespace.py 2016-10-03 17:03:17 +0000 |
681 | @@ -1,4 +1,4 @@ |
682 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
683 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
684 | # GNU Affero General Public License version 3 (see the file LICENSE). |
685 | |
686 | """Implementations of `IGitNamespace`.""" |
687 | @@ -238,6 +238,7 @@ |
688 | has_defaults = False |
689 | allow_push_to_set_default = False |
690 | supports_merge_proposals = False |
691 | + supports_code_imports = False |
692 | allow_recipe_name_from_target = False |
693 | |
694 | def __init__(self, person): |
695 | @@ -316,6 +317,7 @@ |
696 | has_defaults = True |
697 | allow_push_to_set_default = True |
698 | supports_merge_proposals = True |
699 | + supports_code_imports = True |
700 | allow_recipe_name_from_target = True |
701 | |
702 | def __init__(self, person, project): |
703 | @@ -401,6 +403,7 @@ |
704 | has_defaults = True |
705 | allow_push_to_set_default = False |
706 | supports_merge_proposals = True |
707 | + supports_code_imports = True |
708 | allow_recipe_name_from_target = True |
709 | |
710 | def __init__(self, person, distro_source_package): |
711 | |
712 | === modified file 'lib/lp/code/model/hasbranches.py' |
713 | --- lib/lp/code/model/hasbranches.py 2016-10-03 10:54:48 +0000 |
714 | +++ lib/lp/code/model/hasbranches.py 2016-10-03 17:03:17 +0000 |
715 | @@ -16,7 +16,10 @@ |
716 | from zope.component import getUtility |
717 | from zope.security.proxy import removeSecurityProxy |
718 | |
719 | -from lp.code.enums import BranchMergeProposalStatus |
720 | +from lp.code.enums import ( |
721 | + BranchMergeProposalStatus, |
722 | + TargetRevisionControlSystems, |
723 | + ) |
724 | from lp.code.interfaces.branch import DEFAULT_BRANCH_STATUS_IN_LISTING |
725 | from lp.code.interfaces.branchcollection import ( |
726 | IAllBranches, |
727 | @@ -130,5 +133,6 @@ |
728 | owner=None): |
729 | """See `IHasCodeImports`.""" |
730 | return getUtility(ICodeImportSet).new( |
731 | - registrant, self, branch_name, rcs_type, url=url, |
732 | + registrant, self, branch_name, rcs_type, |
733 | + TargetRevisionControlSystems.BZR, url=url, |
734 | cvs_root=cvs_root, cvs_module=cvs_module, owner=owner) |
735 | |
736 | === modified file 'lib/lp/code/model/tests/test_codeimport.py' |
737 | --- lib/lp/code/model/tests/test_codeimport.py 2016-09-30 13:27:27 +0000 |
738 | +++ lib/lp/code/model/tests/test_codeimport.py 2016-10-03 17:03:17 +0000 |
739 | @@ -7,10 +7,15 @@ |
740 | datetime, |
741 | timedelta, |
742 | ) |
743 | +from functools import partial |
744 | |
745 | import pytz |
746 | from sqlobject import SQLObjectNotFound |
747 | from storm.store import Store |
748 | +from testscenarios import ( |
749 | + load_tests_apply_scenarios, |
750 | + WithScenarios, |
751 | + ) |
752 | from zope.component import getUtility |
753 | from zope.security.proxy import removeSecurityProxy |
754 | |
755 | @@ -19,12 +24,14 @@ |
756 | CodeImportResultStatus, |
757 | CodeImportReviewStatus, |
758 | RevisionControlSystems, |
759 | + TargetRevisionControlSystems, |
760 | ) |
761 | from lp.code.errors import ( |
762 | BranchCreatorNotMemberOfOwnerTeam, |
763 | CodeImportAlreadyRequested, |
764 | CodeImportAlreadyRunning, |
765 | CodeImportNotInReviewedState, |
766 | + GitRepositoryCreatorNotMemberOfOwnerTeam, |
767 | ) |
768 | from lp.code.interfaces.branchtarget import IBranchTarget |
769 | from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow |
770 | @@ -51,63 +58,100 @@ |
771 | ) |
772 | |
773 | |
774 | -class TestCodeImportCreation(TestCaseWithFactory): |
775 | +class TestCodeImportBase(WithScenarios, TestCaseWithFactory): |
776 | + |
777 | + scenarios = [ |
778 | + ("Branch", { |
779 | + "target_rcs_type": TargetRevisionControlSystems.BZR, |
780 | + "supports_source_cvs": True, |
781 | + "supports_source_svn": True, |
782 | + "supports_source_bzr": True, |
783 | + }), |
784 | + ("GitRepository", { |
785 | + "target_rcs_type": TargetRevisionControlSystems.GIT, |
786 | + "supports_source_cvs": False, |
787 | + "supports_source_svn": False, |
788 | + "supports_source_bzr": False, |
789 | + }), |
790 | + ] |
791 | + |
792 | + |
793 | +class TestCodeImportCreation(TestCodeImportBase): |
794 | """Test the creation of CodeImports.""" |
795 | |
796 | layer = DatabaseFunctionalLayer |
797 | |
798 | def test_new_svn_import_svn_scheme(self): |
799 | """A subversion import can use the svn:// scheme.""" |
800 | - code_import = CodeImportSet().new( |
801 | + create_func = partial( |
802 | + CodeImportSet().new, |
803 | registrant=self.factory.makePerson(), |
804 | context=self.factory.makeProduct(), |
805 | - branch_name='imported', |
806 | + branch_name=u'imported', |
807 | rcs_type=RevisionControlSystems.BZR_SVN, |
808 | + target_rcs_type=self.target_rcs_type, |
809 | url=self.factory.getUniqueURL(scheme="svn")) |
810 | - self.assertEqual( |
811 | - CodeImportReviewStatus.REVIEWED, |
812 | - code_import.review_status) |
813 | - # No job is created for the import. |
814 | - self.assertIsNot(None, code_import.import_job) |
815 | + if self.supports_source_svn: |
816 | + code_import = create_func() |
817 | + self.assertEqual( |
818 | + CodeImportReviewStatus.REVIEWED, |
819 | + code_import.review_status) |
820 | + # No job is created for the import. |
821 | + self.assertIsNot(None, code_import.import_job) |
822 | + else: |
823 | + self.assertRaises(AssertionError, create_func) |
824 | |
825 | def test_reviewed_svn_import(self): |
826 | """A specific review status can be set for a new import.""" |
827 | - code_import = CodeImportSet().new( |
828 | + create_func = partial( |
829 | + CodeImportSet().new, |
830 | registrant=self.factory.makePerson(), |
831 | context=self.factory.makeProduct(), |
832 | - branch_name='imported', |
833 | + branch_name=u'imported', |
834 | rcs_type=RevisionControlSystems.BZR_SVN, |
835 | + target_rcs_type=self.target_rcs_type, |
836 | url=self.factory.getUniqueURL(), |
837 | review_status=None) |
838 | - self.assertEqual( |
839 | - CodeImportReviewStatus.REVIEWED, |
840 | - code_import.review_status) |
841 | - # A job is created for the import. |
842 | - self.assertIsNot(None, code_import.import_job) |
843 | + if self.supports_source_svn: |
844 | + code_import = create_func() |
845 | + self.assertEqual( |
846 | + CodeImportReviewStatus.REVIEWED, |
847 | + code_import.review_status) |
848 | + # A job is created for the import. |
849 | + self.assertIsNot(None, code_import.import_job) |
850 | + else: |
851 | + self.assertRaises(AssertionError, create_func) |
852 | |
853 | def test_cvs_import_reviewed(self): |
854 | """A new CVS code import should have REVIEWED status.""" |
855 | - code_import = CodeImportSet().new( |
856 | + create_func = partial( |
857 | + CodeImportSet().new, |
858 | registrant=self.factory.makePerson(), |
859 | context=self.factory.makeProduct(), |
860 | - branch_name='imported', |
861 | + branch_name=u'imported', |
862 | rcs_type=RevisionControlSystems.CVS, |
863 | + target_rcs_type=self.target_rcs_type, |
864 | cvs_root=self.factory.getUniqueURL(), |
865 | - cvs_module='module', |
866 | + cvs_module=u'module', |
867 | review_status=None) |
868 | - self.assertEqual( |
869 | - CodeImportReviewStatus.REVIEWED, |
870 | - code_import.review_status) |
871 | - # A job is created for the import. |
872 | - self.assertIsNot(None, code_import.import_job) |
873 | + if self.supports_source_cvs: |
874 | + code_import = create_func() |
875 | + self.assertEqual( |
876 | + CodeImportReviewStatus.REVIEWED, |
877 | + code_import.review_status) |
878 | + # A job is created for the import. |
879 | + self.assertIsNot(None, code_import.import_job) |
880 | + else: |
881 | + self.assertRaises(AssertionError, create_func) |
882 | |
883 | def test_git_import_git_scheme(self): |
884 | """A git import can have a git:// style URL.""" |
885 | code_import = CodeImportSet().new( |
886 | registrant=self.factory.makePerson(), |
887 | context=self.factory.makeProduct(), |
888 | - branch_name='imported', |
889 | + branch_name=u'imported', |
890 | rcs_type=RevisionControlSystems.GIT, |
891 | + target_rcs_type=self.target_rcs_type, |
892 | url=self.factory.getUniqueURL(scheme="git"), |
893 | review_status=None) |
894 | self.assertEqual( |
895 | @@ -121,8 +165,9 @@ |
896 | code_import = CodeImportSet().new( |
897 | registrant=self.factory.makePerson(), |
898 | context=self.factory.makeProduct(), |
899 | - branch_name='imported', |
900 | + branch_name=u'imported', |
901 | rcs_type=RevisionControlSystems.GIT, |
902 | + target_rcs_type=self.target_rcs_type, |
903 | url=self.factory.getUniqueURL(), |
904 | review_status=None) |
905 | self.assertEqual( |
906 | @@ -133,18 +178,24 @@ |
907 | |
908 | def test_bzr_import_reviewed(self): |
909 | """A new bzr import is always reviewed by default.""" |
910 | - code_import = CodeImportSet().new( |
911 | + create_func = partial( |
912 | + CodeImportSet().new, |
913 | registrant=self.factory.makePerson(), |
914 | context=self.factory.makeProduct(), |
915 | - branch_name='mirrored', |
916 | + branch_name=u'mirrored', |
917 | rcs_type=RevisionControlSystems.BZR, |
918 | + target_rcs_type=self.target_rcs_type, |
919 | url=self.factory.getUniqueURL(), |
920 | review_status=None) |
921 | - self.assertEqual( |
922 | - CodeImportReviewStatus.REVIEWED, |
923 | - code_import.review_status) |
924 | - # A job is created for the import. |
925 | - self.assertIsNot(None, code_import.import_job) |
926 | + if self.supports_source_bzr: |
927 | + code_import = create_func() |
928 | + self.assertEqual( |
929 | + CodeImportReviewStatus.REVIEWED, |
930 | + code_import.review_status) |
931 | + # A job is created for the import. |
932 | + self.assertIsNot(None, code_import.import_job) |
933 | + else: |
934 | + self.assertRaises(AssertionError, create_func) |
935 | |
936 | def test_junk_code_import_rejected(self): |
937 | """You are not allowed to create code imports targetting +junk.""" |
938 | @@ -152,8 +203,9 @@ |
939 | self.assertRaises(AssertionError, CodeImportSet().new, |
940 | registrant=registrant, |
941 | context=registrant, |
942 | - branch_name='imported', |
943 | + branch_name=u'imported', |
944 | rcs_type=RevisionControlSystems.GIT, |
945 | + target_rcs_type=self.target_rcs_type, |
946 | url=self.factory.getUniqueURL(), |
947 | review_status=None) |
948 | |
949 | @@ -161,19 +213,27 @@ |
950 | """Test that we can create an import targetting a source package.""" |
951 | registrant = self.factory.makePerson() |
952 | source_package = self.factory.makeSourcePackage() |
953 | + if self.target_rcs_type == TargetRevisionControlSystems.BZR: |
954 | + context = source_package |
955 | + else: |
956 | + context = source_package.distribution_sourcepackage |
957 | code_import = CodeImportSet().new( |
958 | registrant=registrant, |
959 | - context=source_package, |
960 | - branch_name='imported', |
961 | + context=context, |
962 | + branch_name=u'imported', |
963 | rcs_type=RevisionControlSystems.GIT, |
964 | + target_rcs_type=self.target_rcs_type, |
965 | url=self.factory.getUniqueURL(), |
966 | review_status=None) |
967 | code_import = removeSecurityProxy(code_import) |
968 | self.assertEqual(registrant, code_import.registrant) |
969 | - self.assertEqual(registrant, code_import.branch.owner) |
970 | - self.assertEqual( |
971 | - IBranchTarget(source_package), code_import.branch.target) |
972 | - self.assertEqual(source_package, code_import.branch.sourcepackage) |
973 | + if self.target_rcs_type == TargetRevisionControlSystems.BZR: |
974 | + self.assertEqual(registrant, code_import.branch.owner) |
975 | + self.assertEqual(IBranchTarget(context), code_import.branch.target) |
976 | + self.assertEqual(source_package, code_import.branch.sourcepackage) |
977 | + else: |
978 | + self.assertEqual(registrant, code_import.git_repository.owner) |
979 | + self.assertEqual(context, code_import.git_repository.target) |
980 | # And a job is still created |
981 | self.assertIsNot(None, code_import.import_job) |
982 | |
983 | @@ -183,20 +243,29 @@ |
984 | owner = self.factory.makeTeam() |
985 | removeSecurityProxy(registrant).join(owner) |
986 | source_package = self.factory.makeSourcePackage() |
987 | + if self.target_rcs_type == TargetRevisionControlSystems.BZR: |
988 | + context = source_package |
989 | + else: |
990 | + context = source_package.distribution_sourcepackage |
991 | code_import = CodeImportSet().new( |
992 | registrant=registrant, |
993 | - context=source_package, |
994 | - branch_name='imported', |
995 | + context=context, |
996 | + branch_name=u'imported', |
997 | rcs_type=RevisionControlSystems.GIT, |
998 | + target_rcs_type=self.target_rcs_type, |
999 | url=self.factory.getUniqueURL(), |
1000 | review_status=None, owner=owner) |
1001 | code_import = removeSecurityProxy(code_import) |
1002 | self.assertEqual(registrant, code_import.registrant) |
1003 | - self.assertEqual(owner, code_import.branch.owner) |
1004 | - self.assertEqual(registrant, code_import.branch.registrant) |
1005 | - self.assertEqual( |
1006 | - IBranchTarget(source_package), code_import.branch.target) |
1007 | - self.assertEqual(source_package, code_import.branch.sourcepackage) |
1008 | + if self.target_rcs_type == TargetRevisionControlSystems.BZR: |
1009 | + self.assertEqual(owner, code_import.branch.owner) |
1010 | + self.assertEqual(registrant, code_import.branch.registrant) |
1011 | + self.assertEqual(IBranchTarget(context), code_import.branch.target) |
1012 | + self.assertEqual(source_package, code_import.branch.sourcepackage) |
1013 | + else: |
1014 | + self.assertEqual(owner, code_import.git_repository.owner) |
1015 | + self.assertEqual(registrant, code_import.git_repository.registrant) |
1016 | + self.assertEqual(context, code_import.git_repository.target) |
1017 | # And a job is still created |
1018 | self.assertIsNot(None, code_import.import_job) |
1019 | |
1020 | @@ -205,30 +274,39 @@ |
1021 | registrant = self.factory.makePerson() |
1022 | owner = self.factory.makeTeam() |
1023 | source_package = self.factory.makeSourcePackage() |
1024 | + if self.target_rcs_type == TargetRevisionControlSystems.BZR: |
1025 | + context = source_package |
1026 | + expected_exception = BranchCreatorNotMemberOfOwnerTeam |
1027 | + else: |
1028 | + context = source_package.distribution_sourcepackage |
1029 | + expected_exception = GitRepositoryCreatorNotMemberOfOwnerTeam |
1030 | self.assertRaises( |
1031 | - BranchCreatorNotMemberOfOwnerTeam, |
1032 | + expected_exception, |
1033 | CodeImportSet().new, |
1034 | registrant=registrant, |
1035 | - context=source_package, |
1036 | - branch_name='imported', |
1037 | + context=context, |
1038 | + branch_name=u'imported', |
1039 | rcs_type=RevisionControlSystems.GIT, |
1040 | + target_rcs_type=self.target_rcs_type, |
1041 | url=self.factory.getUniqueURL(), |
1042 | review_status=None, owner=owner) |
1043 | |
1044 | |
1045 | -class TestCodeImportDeletion(TestCaseWithFactory): |
1046 | +class TestCodeImportDeletion(TestCodeImportBase): |
1047 | """Test the deletion of CodeImports.""" |
1048 | |
1049 | layer = LaunchpadFunctionalLayer |
1050 | |
1051 | def test_delete(self): |
1052 | """Ensure CodeImport objects can be deleted via CodeImportSet.""" |
1053 | - code_import = self.factory.makeCodeImport() |
1054 | + code_import = self.factory.makeCodeImport( |
1055 | + target_rcs_type=self.target_rcs_type) |
1056 | CodeImportSet().delete(code_import) |
1057 | |
1058 | def test_deleteIncludesJob(self): |
1059 | """Ensure deleting CodeImport objects deletes associated jobs.""" |
1060 | - code_import = self.factory.makeCodeImport() |
1061 | + code_import = self.factory.makeCodeImport( |
1062 | + target_rcs_type=self.target_rcs_type) |
1063 | login_person(getUtility(ILaunchpadCelebrities).vcs_imports.teamowner) |
1064 | job_id = code_import.import_job.id |
1065 | CodeImportJobSet().getById(job_id) |
1066 | @@ -240,7 +318,10 @@ |
1067 | |
1068 | def test_deleteIncludesEvent(self): |
1069 | """Ensure deleting CodeImport objects deletes associated events.""" |
1070 | - code_import_event = self.factory.makeCodeImportEvent() |
1071 | + code_import = self.factory.makeCodeImport( |
1072 | + target_rcs_type=self.target_rcs_type) |
1073 | + code_import_event = self.factory.makeCodeImportEvent( |
1074 | + code_import=code_import) |
1075 | code_import_event_id = code_import_event.id |
1076 | CodeImportSet().delete(code_import_event.code_import) |
1077 | # CodeImportEvent.get should not raise anything. |
1078 | @@ -251,7 +332,10 @@ |
1079 | |
1080 | def test_deleteIncludesResult(self): |
1081 | """Ensure deleting CodeImport objects deletes associated results.""" |
1082 | - code_import_result = self.factory.makeCodeImportResult() |
1083 | + code_import = self.factory.makeCodeImport( |
1084 | + target_rcs_type=self.target_rcs_type) |
1085 | + code_import_result = self.factory.makeCodeImportResult( |
1086 | + code_import=code_import) |
1087 | code_import_result_id = code_import_result.id |
1088 | CodeImportSet().delete(code_import_result.code_import) |
1089 | # CodeImportResult.get should not raise anything. |
1090 | @@ -261,7 +345,7 @@ |
1091 | SQLObjectNotFound, CodeImportResult.get, code_import_result_id) |
1092 | |
1093 | |
1094 | -class TestCodeImportStatusUpdate(TestCaseWithFactory): |
1095 | +class TestCodeImportStatusUpdate(TestCodeImportBase): |
1096 | """Test the status updates of CodeImports.""" |
1097 | |
1098 | layer = DatabaseFunctionalLayer |
1099 | @@ -276,7 +360,8 @@ |
1100 | job.destroySelf() |
1101 | |
1102 | def makeApprovedImportWithPendingJob(self): |
1103 | - code_import = self.factory.makeCodeImport() |
1104 | + code_import = self.factory.makeCodeImport( |
1105 | + target_rcs_type=self.target_rcs_type) |
1106 | code_import.updateFromData( |
1107 | {'review_status': CodeImportReviewStatus.REVIEWED}, |
1108 | self.import_operator) |
1109 | @@ -290,7 +375,8 @@ |
1110 | |
1111 | def test_approve(self): |
1112 | # Approving a code import will create a job for it. |
1113 | - code_import = self.factory.makeCodeImport() |
1114 | + code_import = self.factory.makeCodeImport( |
1115 | + target_rcs_type=self.target_rcs_type) |
1116 | code_import.updateFromData( |
1117 | {'review_status': CodeImportReviewStatus.REVIEWED}, |
1118 | self.import_operator) |
1119 | @@ -300,7 +386,8 @@ |
1120 | |
1121 | def test_suspend_no_job(self): |
1122 | # Suspending a new import has no impact on jobs. |
1123 | - code_import = self.factory.makeCodeImport() |
1124 | + code_import = self.factory.makeCodeImport( |
1125 | + target_rcs_type=self.target_rcs_type) |
1126 | code_import.updateFromData( |
1127 | {'review_status': CodeImportReviewStatus.SUSPENDED}, |
1128 | self.import_operator) |
1129 | @@ -330,7 +417,8 @@ |
1130 | |
1131 | def test_invalidate_no_job(self): |
1132 | # Invalidating a new import has no impact on jobs. |
1133 | - code_import = self.factory.makeCodeImport() |
1134 | + code_import = self.factory.makeCodeImport( |
1135 | + target_rcs_type=self.target_rcs_type) |
1136 | code_import.updateFromData( |
1137 | {'review_status': CodeImportReviewStatus.INVALID}, |
1138 | self.import_operator) |
1139 | @@ -360,7 +448,8 @@ |
1140 | |
1141 | def test_markFailing_no_job(self): |
1142 | # Marking a new import as failing has no impact on jobs. |
1143 | - code_import = self.factory.makeCodeImport() |
1144 | + code_import = self.factory.makeCodeImport( |
1145 | + target_rcs_type=self.target_rcs_type) |
1146 | code_import.updateFromData( |
1147 | {'review_status': CodeImportReviewStatus.FAILING}, |
1148 | self.import_operator) |
1149 | @@ -389,14 +478,15 @@ |
1150 | CodeImportReviewStatus.FAILING, code_import.review_status) |
1151 | |
1152 | |
1153 | -class TestCodeImportResultsAttribute(TestCaseWithFactory): |
1154 | +class TestCodeImportResultsAttribute(TestCodeImportBase): |
1155 | """Test the results attribute of a CodeImport.""" |
1156 | |
1157 | layer = LaunchpadFunctionalLayer |
1158 | |
1159 | def setUp(self): |
1160 | TestCaseWithFactory.setUp(self) |
1161 | - self.code_import = self.factory.makeCodeImport() |
1162 | + self.code_import = self.factory.makeCodeImport( |
1163 | + target_rcs_type=self.target_rcs_type) |
1164 | |
1165 | def tearDown(self): |
1166 | super(TestCodeImportResultsAttribute, self).tearDown() |
1167 | @@ -454,7 +544,7 @@ |
1168 | self.assertEqual(third, results[2]) |
1169 | |
1170 | |
1171 | -class TestConsecutiveFailureCount(TestCaseWithFactory): |
1172 | +class TestConsecutiveFailureCount(TestCodeImportBase): |
1173 | """Tests for `ICodeImport.consecutive_failure_count`.""" |
1174 | |
1175 | layer = LaunchpadZopelessLayer |
1176 | @@ -495,27 +585,31 @@ |
1177 | |
1178 | def test_consecutive_failure_count_zero_initially(self): |
1179 | # A new code import has a consecutive_failure_count of 0. |
1180 | - code_import = self.factory.makeCodeImport() |
1181 | + code_import = self.factory.makeCodeImport( |
1182 | + target_rcs_type=self.target_rcs_type) |
1183 | self.assertEqual(0, code_import.consecutive_failure_count) |
1184 | |
1185 | def test_consecutive_failure_count_succeed(self): |
1186 | # A code import that has succeeded once has a |
1187 | # consecutive_failure_count of 0. |
1188 | - code_import = self.factory.makeCodeImport() |
1189 | + code_import = self.factory.makeCodeImport( |
1190 | + target_rcs_type=self.target_rcs_type) |
1191 | self.succeedImport(code_import) |
1192 | self.assertEqual(0, code_import.consecutive_failure_count) |
1193 | |
1194 | def test_consecutive_failure_count_fail(self): |
1195 | # A code import that has failed once has a consecutive_failure_count |
1196 | # of 1. |
1197 | - code_import = self.factory.makeCodeImport() |
1198 | + code_import = self.factory.makeCodeImport( |
1199 | + target_rcs_type=self.target_rcs_type) |
1200 | self.failImport(code_import) |
1201 | self.assertEqual(1, code_import.consecutive_failure_count) |
1202 | |
1203 | def test_consecutive_failure_count_succeed_succeed_no_changes(self): |
1204 | # A code import that has succeeded then succeeded with no changes has |
1205 | # a consecutive_failure_count of 0. |
1206 | - code_import = self.factory.makeCodeImport() |
1207 | + code_import = self.factory.makeCodeImport( |
1208 | + target_rcs_type=self.target_rcs_type) |
1209 | self.succeedImport(code_import) |
1210 | self.succeedImport( |
1211 | code_import, CodeImportResultStatus.SUCCESS_NOCHANGE) |
1212 | @@ -524,7 +618,8 @@ |
1213 | def test_consecutive_failure_count_succeed_succeed_partial(self): |
1214 | # A code import that has succeeded then succeeded with no changes has |
1215 | # a consecutive_failure_count of 0. |
1216 | - code_import = self.factory.makeCodeImport() |
1217 | + code_import = self.factory.makeCodeImport( |
1218 | + target_rcs_type=self.target_rcs_type) |
1219 | self.succeedImport(code_import) |
1220 | self.succeedImport( |
1221 | code_import, CodeImportResultStatus.SUCCESS_NOCHANGE) |
1222 | @@ -533,7 +628,8 @@ |
1223 | def test_consecutive_failure_count_fail_fail(self): |
1224 | # A code import that has failed twice has a consecutive_failure_count |
1225 | # of 2. |
1226 | - code_import = self.factory.makeCodeImport() |
1227 | + code_import = self.factory.makeCodeImport( |
1228 | + target_rcs_type=self.target_rcs_type) |
1229 | self.failImport(code_import) |
1230 | self.failImport(code_import) |
1231 | self.assertEqual(2, code_import.consecutive_failure_count) |
1232 | @@ -541,7 +637,8 @@ |
1233 | def test_consecutive_failure_count_fail_fail_succeed(self): |
1234 | # A code import that has failed twice then succeeded has a |
1235 | # consecutive_failure_count of 0. |
1236 | - code_import = self.factory.makeCodeImport() |
1237 | + code_import = self.factory.makeCodeImport( |
1238 | + target_rcs_type=self.target_rcs_type) |
1239 | self.failImport(code_import) |
1240 | self.failImport(code_import) |
1241 | self.succeedImport(code_import) |
1242 | @@ -550,7 +647,8 @@ |
1243 | def test_consecutive_failure_count_fail_succeed_fail(self): |
1244 | # A code import that has failed then succeeded then failed again has a |
1245 | # consecutive_failure_count of 1. |
1246 | - code_import = self.factory.makeCodeImport() |
1247 | + code_import = self.factory.makeCodeImport( |
1248 | + target_rcs_type=self.target_rcs_type) |
1249 | self.failImport(code_import) |
1250 | self.succeedImport(code_import) |
1251 | self.failImport(code_import) |
1252 | @@ -559,7 +657,8 @@ |
1253 | def test_consecutive_failure_count_succeed_fail_succeed(self): |
1254 | # A code import that has succeeded then failed then succeeded again |
1255 | # has a consecutive_failure_count of 0. |
1256 | - code_import = self.factory.makeCodeImport() |
1257 | + code_import = self.factory.makeCodeImport( |
1258 | + target_rcs_type=self.target_rcs_type) |
1259 | self.succeedImport(code_import) |
1260 | self.failImport(code_import) |
1261 | self.succeedImport(code_import) |
1262 | @@ -568,8 +667,10 @@ |
1263 | def test_consecutive_failure_count_other_import_non_interference(self): |
1264 | # The failure or success of other code imports does not affect |
1265 | # consecutive_failure_count. |
1266 | - code_import = self.factory.makeCodeImport() |
1267 | - other_import = self.factory.makeCodeImport() |
1268 | + code_import = self.factory.makeCodeImport( |
1269 | + target_rcs_type=self.target_rcs_type) |
1270 | + other_import = self.factory.makeCodeImport( |
1271 | + target_rcs_type=self.target_rcs_type) |
1272 | self.failImport(code_import) |
1273 | self.assertEqual(1, code_import.consecutive_failure_count) |
1274 | self.failImport(other_import) |
1275 | @@ -584,7 +685,7 @@ |
1276 | self.assertEqual(1, code_import.consecutive_failure_count) |
1277 | |
1278 | |
1279 | -class TestTryFailingImportAgain(TestCaseWithFactory): |
1280 | +class TestTryFailingImportAgain(TestCodeImportBase): |
1281 | """Tests for `ICodeImport.tryFailingImportAgain`.""" |
1282 | |
1283 | layer = DatabaseFunctionalLayer |
1284 | @@ -599,6 +700,7 @@ |
1285 | outcomes = {} |
1286 | for status in CodeImportReviewStatus.items: |
1287 | code_import = self.factory.makeCodeImport( |
1288 | + target_rcs_type=self.target_rcs_type, |
1289 | review_status=CodeImportReviewStatus.NEW) |
1290 | code_import.updateFromData( |
1291 | {'review_status': status}, self.factory.makePerson()) |
1292 | @@ -619,7 +721,8 @@ |
1293 | def test_resetsStatus(self): |
1294 | # tryFailingImportAgain sets the review_status of the import back to |
1295 | # REVIEWED. |
1296 | - code_import = self.factory.makeCodeImport() |
1297 | + code_import = self.factory.makeCodeImport( |
1298 | + target_rcs_type=self.target_rcs_type) |
1299 | code_import.updateFromData( |
1300 | {'review_status': CodeImportReviewStatus.FAILING}, |
1301 | self.factory.makePerson()) |
1302 | @@ -630,7 +733,8 @@ |
1303 | |
1304 | def test_requestsImport(self): |
1305 | # tryFailingImportAgain requests an import. |
1306 | - code_import = self.factory.makeCodeImport() |
1307 | + code_import = self.factory.makeCodeImport( |
1308 | + target_rcs_type=self.target_rcs_type) |
1309 | code_import.updateFromData( |
1310 | {'review_status': CodeImportReviewStatus.FAILING}, |
1311 | self.factory.makePerson()) |
1312 | @@ -640,7 +744,7 @@ |
1313 | requester, code_import.import_job.requesting_user) |
1314 | |
1315 | |
1316 | -class TestRequestImport(TestCaseWithFactory): |
1317 | +class TestRequestImport(TestCodeImportBase): |
1318 | """Tests for `ICodeImport.requestImport`.""" |
1319 | |
1320 | layer = DatabaseFunctionalLayer |
1321 | @@ -651,7 +755,8 @@ |
1322 | |
1323 | def test_requestsJob(self): |
1324 | code_import = self.factory.makeCodeImport( |
1325 | - git_repo_url=self.factory.getUniqueURL()) |
1326 | + git_repo_url=self.factory.getUniqueURL(), |
1327 | + target_rcs_type=self.target_rcs_type) |
1328 | requester = self.factory.makePerson() |
1329 | old_date = code_import.import_job.date_due |
1330 | code_import.requestImport(requester) |
1331 | @@ -660,7 +765,8 @@ |
1332 | |
1333 | def test_noop_if_already_requested(self): |
1334 | code_import = self.factory.makeCodeImport( |
1335 | - git_repo_url=self.factory.getUniqueURL()) |
1336 | + git_repo_url=self.factory.getUniqueURL(), |
1337 | + target_rcs_type=self.target_rcs_type) |
1338 | requester = self.factory.makePerson() |
1339 | code_import.requestImport(requester) |
1340 | old_date = code_import.import_job.date_due |
1341 | @@ -672,7 +778,8 @@ |
1342 | |
1343 | def test_optional_error_if_already_requested(self): |
1344 | code_import = self.factory.makeCodeImport( |
1345 | - git_repo_url=self.factory.getUniqueURL()) |
1346 | + git_repo_url=self.factory.getUniqueURL(), |
1347 | + target_rcs_type=self.target_rcs_type) |
1348 | requester = self.factory.makePerson() |
1349 | code_import.requestImport(requester) |
1350 | e = self.assertRaises( |
1351 | @@ -681,10 +788,14 @@ |
1352 | self.assertEqual(requester, e.requesting_user) |
1353 | |
1354 | def test_exception_on_disabled(self): |
1355 | - # get an SVN request which is suspended |
1356 | + # get an SVN/Git (as appropriate) request which is suspended |
1357 | + if self.supports_source_svn: |
1358 | + kwargs = {"svn_branch_url": self.factory.getUniqueURL()} |
1359 | + else: |
1360 | + kwargs = {"git_repo_url": self.factory.getUniqueURL()} |
1361 | code_import = self.factory.makeCodeImport( |
1362 | - svn_branch_url=self.factory.getUniqueURL(), |
1363 | - review_status=CodeImportReviewStatus.SUSPENDED) |
1364 | + target_rcs_type=self.target_rcs_type, |
1365 | + review_status=CodeImportReviewStatus.SUSPENDED, **kwargs) |
1366 | requester = self.factory.makePerson() |
1367 | # which leads to an exception if we try and ask for an import |
1368 | self.assertRaises( |
1369 | @@ -693,10 +804,14 @@ |
1370 | |
1371 | def test_exception_if_already_running(self): |
1372 | code_import = self.factory.makeCodeImport( |
1373 | - git_repo_url=self.factory.getUniqueURL()) |
1374 | + git_repo_url=self.factory.getUniqueURL(), |
1375 | + target_rcs_type=self.target_rcs_type) |
1376 | code_import = make_running_import(factory=self.factory, |
1377 | code_import=code_import) |
1378 | requester = self.factory.makePerson() |
1379 | self.assertRaises( |
1380 | CodeImportAlreadyRunning, code_import.requestImport, |
1381 | requester) |
1382 | + |
1383 | + |
1384 | +load_tests = load_tests_apply_scenarios |
1385 | |
1386 | === modified file 'lib/lp/registry/browser/product.py' |
1387 | --- lib/lp/registry/browser/product.py 2016-09-30 13:27:27 +0000 |
1388 | +++ lib/lp/registry/browser/product.py 2016-10-03 17:03:17 +0000 |
1389 | @@ -145,7 +145,10 @@ |
1390 | from lp.code.browser.codeimport import validate_import_url |
1391 | from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin |
1392 | from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin |
1393 | -from lp.code.enums import RevisionControlSystems |
1394 | +from lp.code.enums import ( |
1395 | + RevisionControlSystems, |
1396 | + TargetRevisionControlSystems, |
1397 | + ) |
1398 | from lp.code.errors import BranchExists |
1399 | from lp.code.interfaces.branch import IBranch |
1400 | from lp.code.interfaces.branchjob import IRosettaUploadJobSource |
1401 | @@ -1988,6 +1991,7 @@ |
1402 | context=self.context, |
1403 | branch_name=branch_name, |
1404 | rcs_type=rcs_item, |
1405 | + target_rcs_type=TargetRevisionControlSystems.BZR, |
1406 | url=url, |
1407 | cvs_root=cvs_root, |
1408 | cvs_module=cvs_module) |
1409 | |
1410 | === modified file 'lib/lp/testing/factory.py' |
1411 | --- lib/lp/testing/factory.py 2016-10-03 17:03:12 +0000 |
1412 | +++ lib/lp/testing/factory.py 2016-10-03 17:03:17 +0000 |
1413 | @@ -114,6 +114,7 @@ |
1414 | GitObjectType, |
1415 | GitRepositoryType, |
1416 | RevisionControlSystems, |
1417 | + TargetRevisionControlSystems, |
1418 | ) |
1419 | from lp.code.errors import UnknownBranchTypeError |
1420 | from lp.code.interfaces.branch import IBranch |
1421 | @@ -2374,21 +2375,28 @@ |
1422 | cvs_module=None, context=None, branch_name=None, |
1423 | git_repo_url=None, |
1424 | bzr_branch_url=None, registrant=None, |
1425 | - rcs_type=None, review_status=None): |
1426 | + rcs_type=None, target_rcs_type=None, |
1427 | + review_status=None): |
1428 | """Create and return a new, arbitrary code import. |
1429 | |
1430 | The type of code import will be inferred from the source details |
1431 | - passed in, but defaults to a Subversion import from an arbitrary |
1432 | - unique URL. |
1433 | + passed in, but defaults to a Subversion->Bazaar import from an |
1434 | + arbitrary unique URL. (If the target type is specified as Git, then |
1435 | + the source type instead defaults to Git.) |
1436 | """ |
1437 | + if target_rcs_type is None: |
1438 | + target_rcs_type = TargetRevisionControlSystems.BZR |
1439 | if (svn_branch_url is cvs_root is cvs_module is git_repo_url is |
1440 | bzr_branch_url is None): |
1441 | - svn_branch_url = self.getUniqueURL() |
1442 | + if target_rcs_type == TargetRevisionControlSystems.BZR: |
1443 | + svn_branch_url = self.getUniqueURL() |
1444 | + else: |
1445 | + git_repo_url = self.getUniqueURL() |
1446 | |
1447 | if context is None: |
1448 | context = self.makeProduct() |
1449 | if branch_name is None: |
1450 | - branch_name = self.getUniqueString('name') |
1451 | + branch_name = self.getUniqueUnicode('name') |
1452 | if registrant is None: |
1453 | registrant = self.makePerson() |
1454 | |
1455 | @@ -2398,23 +2406,27 @@ |
1456 | return code_import_set.new( |
1457 | registrant, context, branch_name, |
1458 | rcs_type=RevisionControlSystems.BZR_SVN, |
1459 | + target_rcs_type=target_rcs_type, |
1460 | url=svn_branch_url, review_status=review_status) |
1461 | elif git_repo_url is not None: |
1462 | assert rcs_type in (None, RevisionControlSystems.GIT) |
1463 | return code_import_set.new( |
1464 | registrant, context, branch_name, |
1465 | rcs_type=RevisionControlSystems.GIT, |
1466 | + target_rcs_type=target_rcs_type, |
1467 | url=git_repo_url, review_status=review_status) |
1468 | elif bzr_branch_url is not None: |
1469 | return code_import_set.new( |
1470 | registrant, context, branch_name, |
1471 | rcs_type=RevisionControlSystems.BZR, |
1472 | + target_rcs_type=target_rcs_type, |
1473 | url=bzr_branch_url, review_status=review_status) |
1474 | else: |
1475 | assert rcs_type in (None, RevisionControlSystems.CVS) |
1476 | return code_import_set.new( |
1477 | registrant, context, branch_name, |
1478 | rcs_type=RevisionControlSystems.CVS, |
1479 | + target_rcs_type=target_rcs_type, |
1480 | cvs_root=cvs_root, cvs_module=cvs_module, |
1481 | review_status=review_status) |
1482 | |
1483 | @@ -2440,9 +2452,10 @@ |
1484 | changelog += entry |
1485 | return self.makeLibraryFileAlias(content=changelog.encode("utf-8")) |
1486 | |
1487 | - def makeCodeImportEvent(self): |
1488 | + def makeCodeImportEvent(self, code_import=None): |
1489 | """Create and return a CodeImportEvent.""" |
1490 | - code_import = self.makeCodeImport() |
1491 | + if code_import is None: |
1492 | + code_import = self.makeCodeImport() |
1493 | person = self.makePerson() |
1494 | code_import_event_set = getUtility(ICodeImportEventSet) |
1495 | return code_import_event_set.newCreate(code_import, person) |
A relatively clean place way to split this would have been s/branch/target/ in an earlier branch. But it's easy enough to scroll over.