Merge lp:~cjwatson/launchpad/codeimport-git-new-view into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18242
Proposed branch: lp:~cjwatson/launchpad/codeimport-git-new-view
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/codeimport-git-worker-fixes
Diff against target: 560 lines (+238/-35)
7 files modified
lib/lp/code/browser/codeimport.py (+59/-16)
lib/lp/code/interfaces/gitnamespace.py (+7/-0)
lib/lp/code/model/codeimport.py (+0/-1)
lib/lp/code/model/gitnamespace.py (+13/-0)
lib/lp/code/model/tests/test_gitnamespace.py (+99/-0)
lib/lp/code/stories/codeimport/xx-create-codeimport.txt (+49/-17)
lib/lp/code/templates/codeimport-new.pt (+11/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/codeimport-git-new-view
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+308545@code.launchpad.net

Commit message

Extend CodeImportNewView to be able to create Git-to-Git code imports.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/codeimport.py'
2--- lib/lp/code/browser/codeimport.py 2016-10-15 01:12:01 +0000
3+++ lib/lp/code/browser/codeimport.py 2016-10-15 02:22:43 +0000
4@@ -68,6 +68,7 @@
5 CodeImportAlreadyRequested,
6 CodeImportAlreadyRunning,
7 CodeImportNotInReviewedState,
8+ GitRepositoryExists,
9 )
10 from lp.code.interfaces.branch import (
11 IBranch,
12@@ -82,6 +83,10 @@
13 ICodeImportSet,
14 )
15 from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet
16+from lp.code.interfaces.gitnamespace import (
17+ get_git_namespace,
18+ IGitNamespacePolicy,
19+ )
20 from lp.registry.interfaces.product import IProduct
21 from lp.registry.interfaces.role import IPersonRoles
22 from lp.services.fields import URIField
23@@ -254,9 +259,10 @@
24 git_repo_url = URIField(
25 title=_("Repo URL"), required=False,
26 description=_(
27- "The URL of the git repository. The HEAD branch will be "
28- "imported. You can import different branches by appending "
29- "',branch=$name' to the URL."),
30+ "The URL of the Git repository. For imports to Bazaar, the "
31+ "HEAD branch will be imported by default, but you can import "
32+ "different branches by appending ',branch=$name' to the URL. "
33+ "For imports to Git, the entire repository will be imported."),
34 allowed_schemes=["git", "http", "https"],
35 allow_userinfo=True,
36 allow_port=True,
37@@ -264,6 +270,13 @@
38 allow_fragment=False,
39 trailing_slash=False)
40
41+ git_target_rcs_type = Choice(
42+ title=_("Target version control system"),
43+ description=_(
44+ "The version control system that the source code should be "
45+ "imported into on the Launchpad side."),
46+ required=False, vocabulary=TargetRevisionControlSystems)
47+
48 bzr_branch_url = URIField(
49 title=_("Branch URL"), required=False,
50 description=_("The URL of the Bazaar branch."),
51@@ -277,10 +290,10 @@
52 branch_name = copy_field(
53 IBranch['name'],
54 __name__='branch_name',
55- title=_('Branch Name'),
56+ title=_('Name'),
57 description=_(
58- "This will be used in the branch URL to identify the "
59- "imported branch. Examples: main, trunk."),
60+ "This will be used in the branch or repository URL to identify "
61+ "the import. Examples: main, trunk."),
62 )
63
64 product = Choice(
65@@ -297,6 +310,7 @@
66 for_input = True
67
68 custom_widget('rcs_type', LaunchpadRadioWidget)
69+ custom_widget('git_target_rcs_type', LaunchpadRadioWidget)
70
71 @property
72 def initial_values(self):
73@@ -304,6 +318,7 @@
74 'owner': self.user,
75 'rcs_type': RevisionControlSystems.BZR,
76 'branch_name': 'trunk',
77+ 'git_target_rcs_type': TargetRevisionControlSystems.BZR,
78 }
79
80 @property
81@@ -365,6 +380,9 @@
82 self.rcs_type_git = str(git_button)
83 self.rcs_type_bzr = str(bzr_button)
84 self.rcs_type_emptymarker = str(empty_marker)
85+ # This widget is only conditionally required in the rcs_type == GIT
86+ # case, but we still don't want a "(nothing selected)" item.
87+ self.widgets['git_target_rcs_type']._displayItemForMissingValue = False
88
89 def _getImportLocation(self, data):
90 """Return the import location based on type."""
91@@ -385,13 +403,18 @@
92 """Create the code import."""
93 product = self.getProduct(data)
94 cvs_root, cvs_module, url = self._getImportLocation(data)
95+ if data['rcs_type'] == RevisionControlSystems.GIT:
96+ target_rcs_type = data.get(
97+ 'git_target_rcs_type', TargetRevisionControlSystems.BZR)
98+ else:
99+ target_rcs_type = TargetRevisionControlSystems.BZR
100 return getUtility(ICodeImportSet).new(
101 registrant=self.user,
102 owner=data['owner'],
103 context=product,
104 branch_name=data['branch_name'],
105 rcs_type=data['rcs_type'],
106- target_rcs_type=TargetRevisionControlSystems.BZR,
107+ target_rcs_type=target_rcs_type,
108 url=url,
109 cvs_root=cvs_root,
110 cvs_module=cvs_module,
111@@ -419,16 +442,19 @@
112 except BranchExists as e:
113 self._setBranchExists(e.existing_branch)
114 return
115+ except GitRepositoryExists as e:
116+ self._setBranchExists(e.existing_repository)
117+ return
118
119 # Subscribe the user.
120- code_import.branch.subscribe(
121+ code_import.target.subscribe(
122 self.user,
123 BranchSubscriptionNotificationLevel.FULL,
124 BranchSubscriptionDiffSize.NODIFF,
125 CodeReviewNotificationLevel.NOEMAIL,
126 self.user)
127
128- self.next_url = canonical_url(code_import.branch)
129+ self.next_url = canonical_url(code_import.target)
130
131 self.request.response.addNotification("""
132 New code import created. The code import will start shortly.""")
133@@ -440,24 +466,41 @@
134 else:
135 return data.get('product')
136
137+ def validate_widgets(self, data, names=None):
138+ """See `LaunchpadFormView`."""
139+ self.widgets['git_target_rcs_type'].context.required = (
140+ data.get('rcs_type') == RevisionControlSystems.GIT)
141+ super(CodeImportNewView, self).validate_widgets(data, names=names)
142+
143 def validate(self, data):
144 """See `LaunchpadFormView`."""
145- # Make sure that the user is able to create branches for the specified
146- # namespace.
147+ rcs_type = data['rcs_type']
148+ if rcs_type == RevisionControlSystems.GIT:
149+ target_rcs_type = data.get(
150+ 'git_target_rcs_type', TargetRevisionControlSystems.BZR)
151+ else:
152+ target_rcs_type = TargetRevisionControlSystems.BZR
153+
154+ # Make sure that the user is able to create branches/repositories
155+ # for the specified namespace.
156 product = self.getProduct(data)
157 # 'owner' in data may be None if it failed validation.
158 owner = data.get('owner')
159 if product is not None and owner is not None:
160- namespace = get_branch_namespace(owner, product)
161- policy = IBranchNamespacePolicy(namespace)
162- if not policy.canCreateBranches(self.user):
163+ if target_rcs_type == TargetRevisionControlSystems.BZR:
164+ namespace = get_branch_namespace(owner, product)
165+ policy = IBranchNamespacePolicy(namespace)
166+ can_create = policy.canCreateBranches(self.user)
167+ else:
168+ namespace = get_git_namespace(product, owner)
169+ policy = IGitNamespacePolicy(namespace)
170+ can_create = policy.canCreateRepositories(self.user)
171+ if not can_create:
172 self.setFieldError(
173 'product',
174 "You are not allowed to register imports for %s."
175 % product.displayname)
176
177- rcs_type = data['rcs_type']
178- target_rcs_type = TargetRevisionControlSystems.BZR
179 # Make sure fields for unselected revision control systems
180 # are blanked out:
181 if rcs_type == RevisionControlSystems.CVS:
182
183=== modified file 'lib/lp/code/interfaces/gitnamespace.py'
184--- lib/lp/code/interfaces/gitnamespace.py 2016-10-12 23:24:24 +0000
185+++ lib/lp/code/interfaces/gitnamespace.py 2016-10-15 02:22:43 +0000
186@@ -104,6 +104,13 @@
187 "Can recipe names reasonably be generated from the target name "
188 "rather than the branch name?")
189
190+ def canCreateRepositories(user):
191+ """Is the user allowed to create repositories for this namespace?
192+
193+ :param user: An `IPerson`.
194+ :return: A Boolean value.
195+ """
196+
197 def getAllowedInformationTypes(who):
198 """Get the information types that a repository in this namespace can
199 have.
200
201=== modified file 'lib/lp/code/model/codeimport.py'
202--- lib/lp/code/model/codeimport.py 2016-10-15 01:47:26 +0000
203+++ lib/lp/code/model/codeimport.py 2016-10-15 02:22:43 +0000
204@@ -63,7 +63,6 @@
205 from lp.code.interfaces.githosting import IGitHostingClient
206 from lp.code.interfaces.gitnamespace import get_git_namespace
207 from lp.code.interfaces.gitrepository import IGitRepository
208-from lp.code.interfaces.hasbranches import IHasCodeImports
209 from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
210 from lp.code.mail.codeimport import code_import_updated
211 from lp.code.model.codeimportjob import CodeImportJobWorkflow
212
213=== modified file 'lib/lp/code/model/gitnamespace.py'
214--- lib/lp/code/model/gitnamespace.py 2016-10-12 23:24:24 +0000
215+++ lib/lp/code/model/gitnamespace.py 2016-10-15 02:22:43 +0000
216@@ -211,6 +211,19 @@
217 match = default
218 return match
219
220+ def canCreateRepositories(self, user):
221+ """See `IGitNamespacePolicy`."""
222+ try:
223+ self.validateRegistrant(user)
224+ except GitRepositoryCreatorNotMemberOfOwnerTeam:
225+ return False
226+ except GitRepositoryCreatorNotOwner:
227+ return False
228+ except GitRepositoryCreationForbidden:
229+ return False
230+ else:
231+ return True
232+
233 def getAllowedInformationTypes(self, who=None):
234 """See `IGitNamespace`."""
235 raise NotImplementedError
236
237=== modified file 'lib/lp/code/model/tests/test_gitnamespace.py'
238--- lib/lp/code/model/tests/test_gitnamespace.py 2016-10-05 10:18:56 +0000
239+++ lib/lp/code/model/tests/test_gitnamespace.py 2016-10-15 02:22:43 +0000
240@@ -520,6 +520,105 @@
241 self.assertEqual(dsp, namespace.target)
242
243
244+class BaseCanCreateRepositoriesMixin:
245+ """Common tests for all namespaces."""
246+
247+ layer = DatabaseFunctionalLayer
248+
249+ def _getNamespace(self, owner):
250+ # Return a namespace appropriate for the owner specified.
251+ raise NotImplementedError(self._getNamespace)
252+
253+ def test_individual(self):
254+ # For a GitNamespace for an individual, only the individual can own
255+ # repositories there.
256+ person = self.factory.makePerson()
257+ namespace = self._getNamespace(person)
258+ self.assertTrue(namespace.canCreateRepositories(person))
259+
260+ def test_other_user(self):
261+ # Any other individual cannot own repositories targeted to the
262+ # person.
263+ person = self.factory.makePerson()
264+ namespace = self._getNamespace(person)
265+ self.assertFalse(
266+ namespace.canCreateRepositories(self.factory.makePerson()))
267+
268+ def test_team_member(self):
269+ # A member of a team is able to create a repository in this
270+ # namespace.
271+ person = self.factory.makePerson()
272+ self.factory.makeTeam(owner=person)
273+ namespace = self._getNamespace(person)
274+ self.assertTrue(namespace.canCreateRepositories(person))
275+
276+ def test_team_non_member(self):
277+ # A person who is not part of the team cannot create repositories
278+ # for the personal team target.
279+ person = self.factory.makePerson()
280+ self.factory.makeTeam(owner=person)
281+ namespace = self._getNamespace(person)
282+ self.assertFalse(
283+ namespace.canCreateRepositories(self.factory.makePerson()))
284+
285+
286+class TestPersonalGitNamespaceCanCreateRepositories(
287+ TestCaseWithFactory, BaseCanCreateRepositoriesMixin):
288+
289+ def _getNamespace(self, owner):
290+ return PersonalGitNamespace(owner)
291+
292+
293+class TestPackageGitNamespaceCanCreateBranches(
294+ TestCaseWithFactory, BaseCanCreateRepositoriesMixin):
295+
296+ def _getNamespace(self, owner):
297+ source_package = self.factory.makeSourcePackage()
298+ return PackageGitNamespace(owner, source_package)
299+
300+
301+class TestProjectGitNamespaceCanCreateBranches(
302+ TestCaseWithFactory, BaseCanCreateRepositoriesMixin):
303+
304+ def _getNamespace(self, owner,
305+ branch_sharing_policy=BranchSharingPolicy.PUBLIC):
306+ project = self.factory.makeProduct(
307+ branch_sharing_policy=branch_sharing_policy)
308+ return ProjectGitNamespace(owner, project)
309+
310+ def setUp(self):
311+ # Setting visibility policies is an admin-only task.
312+ super(TestProjectGitNamespaceCanCreateBranches, self).setUp(
313+ "admin@canonical.com")
314+
315+ def test_any_person(self):
316+ # If there is no privacy set up, any person can create a personal
317+ # branch on the product.
318+ person = self.factory.makePerson()
319+ namespace = self._getNamespace(person, BranchSharingPolicy.PUBLIC)
320+ self.assertTrue(namespace.canCreateRepositories(person))
321+
322+ def test_any_person_with_proprietary_repositories(self):
323+ # If the sharing policy defaults to PROPRIETARY, then non-privileged
324+ # users cannot create a repository.
325+ person = self.factory.makePerson()
326+ namespace = self._getNamespace(person, BranchSharingPolicy.PROPRIETARY)
327+ self.assertFalse(namespace.canCreateRepositories(person))
328+
329+ def test_grantee_with_proprietary_repositories(self):
330+ # If the sharing policy defaults to PROPRIETARY, then non-privileged
331+ # users cannot create a repository.
332+ person = self.factory.makePerson()
333+ other_person = self.factory.makePerson()
334+ team = self.factory.makeTeam(members=[person])
335+ namespace = self._getNamespace(team, BranchSharingPolicy.PROPRIETARY)
336+ getUtility(IService, "sharing").sharePillarInformation(
337+ namespace.target, team, namespace.target.owner,
338+ {InformationType.PROPRIETARY: SharingPermission.ALL})
339+ self.assertTrue(namespace.canCreateRepositories(person))
340+ self.assertFalse(namespace.canCreateRepositories(other_person))
341+
342+
343 class TestNamespaceSet(TestCaseWithFactory):
344 """Tests for `get_namespace`."""
345
346
347=== modified file 'lib/lp/code/stories/codeimport/xx-create-codeimport.txt'
348--- lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2016-10-13 14:46:41 +0000
349+++ lib/lp/code/stories/codeimport/xx-create-codeimport.txt 2016-10-15 02:22:43 +0000
350@@ -66,7 +66,7 @@
351 The user is required to enter a project that the import is for, a name
352 for the import branch, and a Bazaar branch location.
353
354- >>> browser.getControl('Branch Name').value = "mirrored"
355+ >>> browser.getControl('Name').value = "mirrored"
356 >>> browser.getControl('Branch URL', index=0).value = (
357 ... "http://bzr.example.com/firefox/trunk")
358 >>> browser.getControl('Request Import').click()
359@@ -87,7 +87,7 @@
360 URL.
361
362 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
363- >>> browser.getControl('Branch Name').value = "with-password"
364+ >>> browser.getControl('Name').value = "with-password"
365 >>> browser.getControl('Branch URL', index=0).value = (
366 ... "http://user:password@bzr.example.com/firefox/trunk")
367 >>> browser.getControl('Project').value = "firefox"
368@@ -102,7 +102,7 @@
369 Specifying a Launchpad URL results in an error.
370
371 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
372- >>> browser.getControl('Branch Name').value = "invalid"
373+ >>> browser.getControl('Name').value = "invalid"
374 >>> browser.getControl('Branch URL', index=0).value = (
375 ... "http://bazaar.launchpad.net/firefox/trunk")
376 >>> browser.getControl('Project').value = "firefox"
377@@ -116,7 +116,7 @@
378
379 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
380 >>> browser.getControl('Project').value = "firefox"
381- >>> browser.getControl('Branch Name').value = "lp-git-import"
382+ >>> browser.getControl('Name').value = "lp-git-import"
383 >>> browser.getControl('Git').click()
384 >>> browser.getControl('Repo URL', index=0).value = (
385 ... "git://git.launchpad.net/firefox.git")
386@@ -136,7 +136,7 @@
387 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
388 >>> browser.getControl('Subversion').click()
389 >>> browser.getControl('Project').value = "firefox"
390- >>> browser.getControl('Branch Name').value = "imported"
391+ >>> browser.getControl('Name').value = "imported"
392 >>> browser.getControl('Branch URL', index=1).value = (
393 ... "http://svn.example.com/firefox/trunk")
394 >>> browser.getControl('Request Import').click()
395@@ -167,7 +167,7 @@
396
397 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
398 >>> browser.getControl('Subversion').click()
399- >>> browser.getControl('Branch Name').value = "svn-with-password"
400+ >>> browser.getControl('Name').value = "svn-with-password"
401 >>> browser.getControl('Branch URL', index=1).value = (
402 ... "http://user:password@svn.example.com/firefox/trunk")
403 >>> browser.getControl('Project').value = "firefox"
404@@ -180,21 +180,21 @@
405 as soon as possible.
406
407
408-Requesting a Git import
409-=======================
410+Requesting a Git-to-Bazaar import
411+=================================
412
413 The user is required to enter a project that the import is for,
414-a name for the import branch, and a subversion branch location.
415+a name for the import branch, and a Git repository location.
416
417 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
418 >>> browser.getControl('Project').value = "firefox"
419- >>> browser.getControl('Branch Name').value = "git-import"
420+ >>> browser.getControl('Name').value = "git-import"
421 >>> browser.getControl('Git').click()
422 >>> browser.getControl('Repo URL', index=0).value = (
423 ... "git://example.com/firefox.git")
424 >>> browser.getControl('Request Import').click()
425
426-When the user clicks continue, the approved import branch is created
427+When the user clicks continue, the approved import branch is created.
428
429 >>> print extract_text(find_tag_by_id(browser.contents, "import-details"))
430 Import Status: Reviewed
431@@ -203,6 +203,38 @@
432 The next import is scheduled to run as soon as possible.
433
434
435+Requesting a Git-to-Git import
436+==============================
437+
438+The user is required to enter a project that the import is for,
439+a name for the import repository, and a Git repository location.
440+
441+ >>> from lp.code.interfaces.codeimport import (
442+ ... CODE_IMPORT_GIT_TARGET_FEATURE_FLAG,
443+ ... )
444+ >>> from lp.code.tests.helpers import GitHostingFixture
445+ >>> from lp.services.features.testing import FeatureFixture
446+
447+ >>> with FeatureFixture({CODE_IMPORT_GIT_TARGET_FEATURE_FLAG: u'on'}):
448+ ... browser.open("http://code.launchpad.dev/+code-imports/+new")
449+ ... browser.getControl('Project').value = "firefox"
450+ ... browser.getControl('Name').value = "upstream"
451+ ... browser.getControl('Git', index=0).click()
452+ ... browser.getControl('Git', index=1).click()
453+ ... browser.getControl('Repo URL', index=0).value = (
454+ ... "git://example.com/firefox2.git")
455+ ... with GitHostingFixture():
456+ ... browser.getControl('Request Import').click()
457+
458+When the user clicks continue, the approved import repository is created.
459+
460+ >>> print extract_text(find_tag_by_id(browser.contents, "import-details"))
461+ Import Status: Reviewed
462+ This repository is an import of the Git repository at
463+ git://example.com/firefox2.git.
464+ The next import is scheduled to run as soon as possible.
465+
466+
467 Requesting a CVS import
468 =======================
469
470@@ -211,7 +243,7 @@
471
472 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
473 >>> browser.getControl('Project').value = "firefox"
474- >>> browser.getControl('Branch Name').value = "import2"
475+ >>> browser.getControl('Name').value = "import2"
476 >>> browser.getControl('CVS').click()
477 >>> browser.getControl('Repository').value = (
478 ... ":pserver:anonymous@cvs.example.com:/mozilla/cvs")
479@@ -233,7 +265,7 @@
480
481 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
482 >>> browser.getControl('Project').value = "firefox"
483- >>> browser.getControl('Branch Name').value = "import2"
484+ >>> browser.getControl('Name').value = "import2"
485 >>> browser.getControl('CVS').click()
486 >>> browser.getControl('Repository').value = (
487 ... ":anonymous@cvs.example.com:/mozilla/cvs")
488@@ -256,7 +288,7 @@
489 The error is shown even if the project is different.
490
491 >>> browser.getControl('Project').value = "thunderbird"
492- >>> browser.getControl('Branch Name').value = "imported"
493+ >>> browser.getControl('Name').value = "imported"
494 >>> browser.getControl('CVS').click()
495 >>> browser.getControl('Repository').value = (
496 ... ":pserver:anonymous@cvs.example.com:/mozilla/cvs")
497@@ -290,7 +322,7 @@
498 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
499 >>> browser.getControl('Subversion').click()
500 >>> browser.getControl('Project').value = "firefox"
501- >>> browser.getControl('Branch Name').value = "imported"
502+ >>> browser.getControl('Name').value = "imported"
503 >>> browser.getControl('Branch URL', index=1).value = (
504 ... "http://svn.example.com/firefox/other")
505 >>> browser.getControl('Request Import').click()
506@@ -307,7 +339,7 @@
507
508 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
509 >>> browser.getControl('Project').value = "launchpad"
510- >>> browser.getControl('Branch Name').value = "imported"
511+ >>> browser.getControl('Name').value = "imported"
512 >>> browser.getControl('Branch URL', index=0).value = (
513 ... "http://svn.example.com/launchpage/fake")
514 >>> browser.getControl('Request Import').click()
515@@ -324,7 +356,7 @@
516
517 >>> browser.open("http://code.launchpad.dev/+code-imports/+new")
518 >>> browser.getControl('Project').value = "no-such-product"
519- >>> browser.getControl('Branch Name').value = "imported"
520+ >>> browser.getControl('Name').value = "imported"
521 >>> browser.getControl('Branch URL', index=0).value = (
522 ... "http://svn.example.com/launchpage/fake")
523 >>> browser.getControl('Request Import').click()
524
525=== modified file 'lib/lp/code/templates/codeimport-new.pt'
526--- lib/lp/code/templates/codeimport-new.pt 2012-10-09 01:07:52 +0000
527+++ lib/lp/code/templates/codeimport-new.pt 2016-10-15 02:22:43 +0000
528@@ -45,7 +45,7 @@
529 <tr>
530 <td colspan="2">
531 <div class="formHelp">
532- Enter details for the selected repository type.
533+ Enter details for the selected version control system.
534 </div>
535 </td>
536 </tr>
537@@ -74,6 +74,10 @@
538 <tal:widget define="widget nocall:view/widgets/git_repo_url">
539 <metal:block use-macro="context/@@launchpad_form/widget_row" />
540 </tal:widget>
541+ <tal:widget define="widget nocall:view/widgets/git_target_rcs_type"
542+ condition="request/features/code.import.git_target">
543+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
544+ </tal:widget>
545 </table>
546 </td>
547 </tr>
548@@ -131,6 +135,12 @@
549 }
550 }
551 updateField(form['field.git_repo_url'], rcs_type === 'GIT');
552+ for (i = 0; i < form.elements.length; i++) {
553+ if (form.elements[i].id.startsWith(
554+ 'field.git_target_rcs_type.')) {
555+ updateField(form.elements[i], rcs_type === 'GIT');
556+ }
557+ }
558 updateField(form['field.cvs_root'], rcs_type === 'CVS');
559 updateField(form['field.cvs_module'], rcs_type === 'CVS');
560 updateField(form['field.svn_branch_url'], rcs_type === 'BZR_SVN');