Merge lp:~cjwatson/launchpad/codeimport-git-configure-code into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18260
Proposed branch: lp:~cjwatson/launchpad/codeimport-git-configure-code
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/codeimport-git-refactor-name-validation
Diff against target: 745 lines (+335/-120)
6 files modified
lib/lp/code/javascript/productseries-setbranch.js (+26/-16)
lib/lp/code/javascript/tests/test_productseries-setbranch.html (+1/-1)
lib/lp/code/templates/configure-code.pt (+41/-14)
lib/lp/registry/browser/product.py (+197/-83)
lib/lp/registry/browser/tests/productseries-setbranch-view.txt (+3/-3)
lib/lp/registry/browser/tests/test_product.py (+67/-3)
To merge this branch: bzr merge lp:~cjwatson/launchpad/codeimport-git-configure-code
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+308577@code.launchpad.net

Commit message

Extend Product:+configure-code 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/javascript/productseries-setbranch.js'
2--- lib/lp/code/javascript/productseries-setbranch.js 2015-06-25 07:39:40 +0000
3+++ lib/lp/code/javascript/productseries-setbranch.js 2016-11-11 14:59:36 +0000
4@@ -87,32 +87,39 @@
5 module.onclick_branch_type = function(e) {
6 /* Which branch type radio button was selected? */
7 var selectedRCS = module._get_selected_rcs();
8- var types = document.getElementsByName('field.branch_type');
9- var type = 'None';
10- var i;
11- for (i = 0; i < types.length; i++) {
12- if (types[i].checked) {
13- type = types[i].value;
14- break;
15- }
16- }
17+ var type_node = Y.one('input[name="field.branch_type"]:checked');
18+ var type = (type_node === null) ? 'None' : type_node.get('value');
19 // Linked
20- module.set_enabled('field.branch_location', type === 'link-lp-bzr');
21- module.set_enabled('field.branch_name', type !== 'link-lp-bzr');
22- module.set_enabled('field.branch_owner', type !== 'link-lp-bzr');
23- // New, empty branch.
24+ module.set_enabled('field.branch_location', type === 'link-lp');
25+ module.set_enabled('field.branch_name', type !== 'link-lp');
26+ module.set_enabled('field.branch_owner', type !== 'link-lp');
27 // Import
28 var is_external = (type === 'import-external');
29 module.set_enabled('field.repo_url', is_external);
30 module.set_enabled('field.cvs_module',
31 (is_external & selectedRCS === 'CVS'));
32 var rcs_types = module._rcs_types();
33- var j;
34- for (j = 0; j < rcs_types.length; j++) {
35- rcs_types[j].disabled = !is_external;
36+ var i;
37+ for (i = 0; i < rcs_types.length; i++) {
38+ rcs_types[i].disabled = !is_external;
39 }
40 };
41
42+ module.onclick_git_repository_type = function(e) {
43+ /* Which Git repository type radio button was selected? */
44+ var type_node = Y.one(
45+ 'input[name="field.git_repository_type"]:checked');
46+ var type = (type_node === null) ? 'None' : type_node.get('value');
47+ // Linked
48+ module.set_enabled(
49+ 'field.git_repository_location', type === 'link-lp');
50+ module.set_enabled('field.git_repository_name', type !== 'link-lp');
51+ module.set_enabled('field.git_repository_owner', type !== 'link-lp');
52+ // Import
53+ var is_external = (type === 'import-external');
54+ module.set_enabled('field.git_repository_url', is_external);
55+ };
56+
57 module.setup = function() {
58 Y.all('input[name="field.rcs_type"]').on(
59 'click', module.onclick_rcs_type);
60@@ -120,6 +127,8 @@
61 'click', module.onclick_branch_type);
62 Y.all('input[name="field.default_vcs"]').on(
63 'click', module.onclick_default_vcs);
64+ Y.all('input[name="field.git_repository_type"]').on(
65+ 'click', module.onclick_git_repository_type);
66
67 // Set the initial state.
68 module.onclick_rcs_type();
69@@ -128,6 +137,7 @@
70 if (document.getElementById('default_vcs')) {
71 module.setup_expanders();
72 module.onclick_default_vcs();
73+ module.onclick_git_repository_type();
74 }
75 };
76
77
78=== modified file 'lib/lp/code/javascript/tests/test_productseries-setbranch.html'
79--- lib/lp/code/javascript/tests/test_productseries-setbranch.html 2012-10-26 09:54:28 +0000
80+++ lib/lp/code/javascript/tests/test_productseries-setbranch.html 2016-11-11 14:59:36 +0000
81@@ -61,7 +61,7 @@
82 <label>
83 <input class="radioType" checked="checked"
84 id="field.branch_type.0"
85- name="field.branch_type" type="radio" value="link-lp-bzr" />
86+ name="field.branch_type" type="radio" value="link-lp" />
87 Link to a Bazaar branch already in Launchpad
88 </label>
89 <table>
90
91=== modified file 'lib/lp/code/templates/configure-code.pt'
92--- lib/lp/code/templates/configure-code.pt 2016-04-28 02:09:21 +0000
93+++ lib/lp/code/templates/configure-code.pt 2016-11-11 14:59:36 +0000
94@@ -51,10 +51,10 @@
95 <div metal:use-macro="context/@@+configure-code-macros/no-keys"></div>
96 </div>
97
98+ <h3>Link or import an existing branch</h3>
99 <table id="form_bzr" class="form">
100 <tr>
101 <td>
102- <h3>Link or import an existing branch</h3>
103 <label tal:replace="structure view/branch_type_link">
104 Link to a Bazaar branch already on Launchpad
105 </label>
106@@ -133,20 +133,47 @@
107 <span class="sprite gitbranch">Git settings</span>
108 </a>
109 <div id="git-expander-content">
110- <div id="form_git" class="form">
111- <div class="push-instructions">
112- <div metal:use-macro="context/@@+configure-code-macros/push-instructions-git"></div>
113- <div metal:use-macro="context/@@+configure-code-macros/no-keys"></div>
114- </div>
115-
116- <h3>Link an existing repository</h3>
117- <p>Link to an existing Git repository already on Launchpad</p>
118- <table>
119- <tal:widget define="widget nocall:view/widgets/git_repository_location">
120- <metal:block use-macro="context/@@launchpad_form/widget_row" />
121- </tal:widget>
122- </table>
123+ <div class="push-instructions">
124+ <div metal:use-macro="context/@@+configure-code-macros/push-instructions-git"></div>
125+ <div metal:use-macro="context/@@+configure-code-macros/no-keys"></div>
126 </div>
127+
128+ <h3>Link or import an existing repository</h3>
129+ <table id="form_git" class="form">
130+ <tr>
131+ <td>
132+ <label tal:replace="structure view/git_repository_type_link">
133+ Link to a Git repository already on Launchpad
134+ </label>
135+ <table class="subordinate">
136+ <tal:widget define="widget nocall:view/widgets/git_repository_location">
137+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
138+ </tal:widget>
139+ </table>
140+ </td>
141+ </tr>
142+
143+ <tr id="git_mirror"
144+ tal:condition="request/features/code.import.git_target">
145+ <td>
146+ <label tal:replace="structure view/git_repository_type_import">
147+ Import a repository hosted somewhere else
148+ </label>
149+ <table class="subordinate">
150+ <tal:widget define="widget nocall:view/widgets/git_repository_name">
151+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
152+ </tal:widget>
153+ <tal:widget define="widget nocall:view/widgets/git_repository_owner">
154+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
155+ </tal:widget>
156+
157+ <tal:widget define="widget nocall:view/widgets/git_repository_url">
158+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
159+ </tal:widget>
160+ </table>
161+ </td>
162+ </tr>
163+ </table>
164 </div>
165 </div>
166 </tal:block>
167
168=== modified file 'lib/lp/registry/browser/product.py'
169--- lib/lp/registry/browser/product.py 2016-10-15 13:41:19 +0000
170+++ lib/lp/registry/browser/product.py 2016-11-11 14:59:36 +0000
171@@ -151,14 +151,20 @@
172 RevisionControlSystems,
173 TargetRevisionControlSystems,
174 )
175-from lp.code.errors import BranchExists
176+from lp.code.errors import (
177+ BranchExists,
178+ GitRepositoryExists,
179+ )
180 from lp.code.interfaces.branch import IBranch
181 from lp.code.interfaces.branchjob import IRosettaUploadJobSource
182 from lp.code.interfaces.codeimport import (
183 ICodeImport,
184 ICodeImportSet,
185 )
186-from lp.code.interfaces.gitrepository import IGitRepositorySet
187+from lp.code.interfaces.gitrepository import (
188+ IGitRepository,
189+ IGitRepositorySet,
190+ )
191 from lp.registry.browser import (
192 add_subscribe_link,
193 BaseRdfView,
194@@ -1658,18 +1664,26 @@
195 return BatchNavigator(decorated_result, self.request)
196
197
198-LINK_LP_BZR = 'link-lp-bzr'
199+LINK_LP = 'link-lp'
200 IMPORT_EXTERNAL = 'import-external'
201
202
203 BRANCH_TYPE_VOCABULARY = SimpleVocabulary((
204- SimpleTerm(LINK_LP_BZR, LINK_LP_BZR,
205+ SimpleTerm(LINK_LP, LINK_LP,
206 _("Link to a Bazaar branch already on Launchpad")),
207 SimpleTerm(IMPORT_EXTERNAL, IMPORT_EXTERNAL,
208 _("Import a branch hosted somewhere else")),
209 ))
210
211
212+GIT_REPOSITORY_TYPE_VOCABULARY = SimpleVocabulary((
213+ SimpleTerm(LINK_LP, LINK_LP,
214+ _("Link to a Git repository already on Launchpad")),
215+ SimpleTerm(IMPORT_EXTERNAL, IMPORT_EXTERNAL,
216+ _("Import a Git repository hosted somewhere else")),
217+ ))
218+
219+
220 class SetBranchForm(Interface):
221 """The fields presented on the form for setting a branch."""
222
223@@ -1696,7 +1710,7 @@
224
225 branch_type = Choice(
226 title=_('Import type'), vocabulary=BRANCH_TYPE_VOCABULARY,
227- description=_("The type of import"), required=True)
228+ description=_("Whether the branch is imported"), required=True)
229
230 branch_name = copy_field(
231 IBranch['name'], __name__='branch_name', title=_('Branch name'),
232@@ -1706,23 +1720,37 @@
233 IBranch['owner'], __name__='branch_owner', title=_('Branch owner'),
234 description=_(''), required=True)
235
236-
237-def create_git_fields():
238- return form.Fields(
239- Choice(__name__='default_vcs',
240- title=_("Project VCS"),
241- required=True, vocabulary=VCSType,
242- description=_("The version control system for "
243- "this project.")),
244- Choice(__name__='git_repository_location',
245- title=_('Git repository'),
246- required=False,
247- vocabulary='GitRepositoryRestrictedOnProduct',
248- description=_(
249- "The Git repository for this project in Launchpad, "
250- "if one exists, in the form: "
251- "~user/project-name/+git/repo-name"))
252- )
253+ default_vcs = Choice(
254+ title=_("Project VCS"), required=True, vocabulary=VCSType,
255+ description=_("The version control system for this project."))
256+
257+ git_repository_location = Choice(
258+ title=_('Git repository'), required=False,
259+ vocabulary='GitRepositoryRestrictedOnProduct',
260+ description=_(
261+ "The Git repository for this project in Launchpad, "
262+ "if one exists, in the form: "
263+ "~user/project-name/+git/repo-name"))
264+
265+ git_repository_type = Choice(
266+ title=_('Import type'), required=True,
267+ vocabulary=GIT_REPOSITORY_TYPE_VOCABULARY,
268+ description=_('Whether the repository is imported'))
269+
270+ git_repository_name = copy_field(
271+ IGitRepository['name'], __name__='git_repository_name',
272+ title=_('Git repository name'), description=_(''), required=True)
273+
274+ git_repository_owner = copy_field(
275+ IGitRepository['owner'], __name__='git_repository_owner',
276+ title=_('Git repository owner'), description=_(''), required=True)
277+
278+ git_repository_url = URIField(
279+ title=_('Git repository URL'), required=True,
280+ description=_('The URL of the Git repository.'),
281+ allowed_schemes=["git", "http", "https"],
282+ allow_userinfo=True, allow_port=True, allow_query=False,
283+ allow_fragment=False, trailing_slash=False)
284
285
286 class ProductSetBranchView(ReturnToReferrerMixin, LaunchpadFormView,
287@@ -1740,6 +1768,7 @@
288 custom_widget('rcs_type', LaunchpadRadioWidget)
289 custom_widget('branch_type', LaunchpadRadioWidget)
290 custom_widget('default_vcs', LaunchpadRadioWidget)
291+ custom_widget('git_repository_type', LaunchpadRadioWidget)
292
293 errors_in_action = False
294 is_series = False
295@@ -1754,8 +1783,9 @@
296 return dict(
297 rcs_type=RevisionControlSystems.BZR,
298 default_vcs=(self.context.pillar.inferred_vcs or VCSType.BZR),
299- branch_type=LINK_LP_BZR,
300+ branch_type=LINK_LP,
301 branch_location=self.series.branch,
302+ git_repository_type=LINK_LP,
303 git_repository_location=repository_set.getDefaultRepository(
304 self.context.pillar))
305
306@@ -1781,8 +1811,11 @@
307 def setUpFields(self):
308 """See `LaunchpadFormView`."""
309 super(ProductSetBranchView, self).setUpFields()
310- if not self.is_series:
311- self.form_fields = (self.form_fields + create_git_fields())
312+ if self.is_series:
313+ self.form_fields = self.form_fields.omit(
314+ 'default_vcs', 'git_repository_location',
315+ 'git_repository_type', 'git_repository_name',
316+ 'git_repository_owner', 'git_repository_url')
317
318 def setUpWidgets(self):
319 """See `LaunchpadFormView`."""
320@@ -1803,11 +1836,9 @@
321 widget = self.widgets['branch_type']
322 current_value = widget._getFormValue()
323 vocab = widget.vocabulary
324-
325- (self.branch_type_link,
326- self.branch_type_import) = [
327+ self.branch_type_link, self.branch_type_import = [
328 render_radio_widget_part(widget, value, current_value)
329- for value in (LINK_LP_BZR, IMPORT_EXTERNAL)]
330+ for value in (LINK_LP, IMPORT_EXTERNAL)]
331
332 if not self.is_series:
333 widget = self.widgets['default_vcs']
334@@ -1818,14 +1849,21 @@
335 self.default_vcs_bzr = render_radio_widget_part(
336 widget, vocab.BZR, current_value, 'Bazaar')
337
338+ widget = self.widgets['git_repository_type']
339+ current_value = widget._getFormValue()
340+ vocab = widget.vocabulary
341+ self.git_repository_type_link, self.git_repository_type_import = [
342+ render_radio_widget_part(widget, value, current_value)
343+ for value in (LINK_LP, IMPORT_EXTERNAL)]
344+
345 def _validateLinkLpBzr(self, data):
346- """Validate data for link-lp-bzr case."""
347+ """Validate data for link-lp bzr case."""
348 if 'branch_location' not in data:
349 self.setFieldError(
350 'branch_location', 'The branch location must be set.')
351
352 def _validateLinkLpGit(self, data):
353- """Validate data for link-lp-git case."""
354+ """Validate data for link-lp git case."""
355 if data.get('git_repository_location'):
356 repo = data.get('git_repository_location')
357 if not repo:
358@@ -1833,8 +1871,8 @@
359 'git_repository_location',
360 'The repository does not exist.')
361
362- def _validateImportExternal(self, data):
363- """Validate data for import external case."""
364+ def _validateImportExternalBzr(self, data):
365+ """Validate data for import-external bzr case."""
366 rcs_type = data.get('rcs_type')
367 repo_url = data.get('repo_url')
368
369@@ -1864,15 +1902,41 @@
370 elif rcs_type == RevisionControlSystems.CVS:
371 if 'cvs_module' not in data:
372 self.setFieldError('cvs_module', 'The CVS module must be set.')
373- self._validateBranch(data)
374
375- def _validateBranch(self, data):
376- """Validate that branch name and owner are set."""
377 if 'branch_name' not in data:
378 self.setFieldError('branch_name', 'The branch name must be set.')
379 if 'branch_owner' not in data:
380 self.setFieldError('branch_owner', 'The branch owner must be set.')
381
382+ def _validateImportExternalGit(self, data):
383+ """Validate data for import-external git case."""
384+ git_repository_url = data.get('git_repository_url')
385+
386+ # Private teams are forbidden from owning code imports.
387+ git_repository_owner = data.get('git_repository_owner')
388+ if git_repository_owner is not None and git_repository_owner.private:
389+ self.setFieldError(
390+ 'git_repository_owner',
391+ 'Private teams are forbidden from owning external imports.')
392+
393+ if git_repository_url is None:
394+ self.setFieldError(
395+ 'git_repository_url',
396+ 'You must set the external repository URL.')
397+ else:
398+ reason = validate_import_url(
399+ git_repository_url, RevisionControlSystems.GIT,
400+ TargetRevisionControlSystems.GIT)
401+ if reason:
402+ self.setFieldError('git_repository_url', reason)
403+
404+ if 'git_repository_name' not in data:
405+ self.setFieldError(
406+ 'git_repository_name', 'The repository name must be set.')
407+ if 'git_repository_owner' not in data:
408+ self.setFieldError(
409+ 'git_repository_owner', 'The repository owner must be set.')
410+
411 def _setRequired(self, names, value):
412 """Mark the widget field as optional."""
413 for name in names:
414@@ -1897,11 +1961,25 @@
415
416 def validate_widgets(self, data, names=None):
417 """See `LaunchpadFormView`."""
418- names = ['branch_type', 'rcs_type', 'default_vcs']
419+ names = [
420+ 'branch_type', 'rcs_type', 'default_vcs', 'git_repository_type']
421 super(ProductSetBranchView, self).validate_widgets(data, names)
422+
423+ if not self.is_series:
424+ git_repository_type = data.get('git_repository_type')
425+ if git_repository_type == LINK_LP:
426+ # Mark other widgets as non-required.
427+ self._setRequired(['git_repository_url', 'git_repository_name',
428+ 'git_repository_owner'], False)
429+ elif git_repository_type == IMPORT_EXTERNAL:
430+ # The repository location is not required for validation.
431+ self._setRequired(['git_repository_location'], False)
432+ else:
433+ raise AssertionError(
434+ "Unknown Git repository type %s" % git_repository_type)
435+
436 branch_type = data.get('branch_type')
437-
438- if branch_type == LINK_LP_BZR:
439+ if branch_type == LINK_LP:
440 # Mark other widgets as non-required.
441 self._setRequired(['rcs_type', 'repo_url', 'cvs_module',
442 'branch_name', 'branch_owner'], False)
443@@ -1918,6 +1996,7 @@
444 self._setRequired(['cvs_module'], True)
445 else:
446 raise AssertionError("Unknown branch type %s" % branch_type)
447+
448 # Perform full validation now.
449 super(ProductSetBranchView, self).validate_widgets(data)
450
451@@ -1927,13 +2006,22 @@
452 # continue as we'd likely just override the errors reported there.
453 if len(self.errors) > 0:
454 return
455+
456+ if not self.is_series:
457+ git_repository_type = data.get('git_repository_type')
458+ if git_repository_type == LINK_LP:
459+ self._validateLinkLpGit(data)
460+ elif git_repository_type == IMPORT_EXTERNAL:
461+ self._validateImportExternalGit(data)
462+ else:
463+ raise AssertionError(
464+ "Unknown Git repository type %s" % git_repository_type)
465+
466 branch_type = data.get('branch_type')
467- if not self.is_series:
468- self._validateLinkLpGit(data)
469- if branch_type == IMPORT_EXTERNAL:
470- self._validateImportExternal(data)
471- elif branch_type == LINK_LP_BZR:
472+ if branch_type == LINK_LP:
473 self._validateLinkLpBzr(data)
474+ elif branch_type == IMPORT_EXTERNAL:
475+ self._validateImportExternalBzr(data)
476 else:
477 raise AssertionError("Unknown branch type %s" % branch_type)
478
479@@ -1959,54 +2047,80 @@
480 if default_vcs:
481 self.context.vcs = default_vcs
482
483- repo = data.get('git_repository_location')
484- getUtility(IGitRepositorySet).setDefaultRepository(
485- self.context, repo)
486- if branch_type == LINK_LP_BZR:
487+ git_repository_type = data.get('git_repository_type')
488+
489+ if git_repository_type == LINK_LP:
490+ repo = data.get('git_repository_location')
491+ repository_set = getUtility(IGitRepositorySet)
492+ if repository_set.getDefaultRepository(self.context) != repo:
493+ repository_set.setDefaultRepository(self.context, repo)
494+ self.add_update_notification()
495+ elif git_repository_type == IMPORT_EXTERNAL:
496+ name = data.get('git_repository_name')
497+ owner = data.get('git_repository_owner')
498+ url = data.get('git_repository_url')
499+ try:
500+ code_import = getUtility(ICodeImportSet).new(
501+ owner=owner,
502+ registrant=self.user,
503+ context=self.context,
504+ branch_name=name,
505+ rcs_type=RevisionControlSystems.GIT,
506+ target_rcs_type=TargetRevisionControlSystems.GIT,
507+ url=url)
508+ except GitRepositoryExists as e:
509+ self._setBranchExists(
510+ e.existing_repository, 'git_repository_name')
511+ self.abort_update()
512+ return
513+ getUtility(IGitRepositorySet).setDefaultRepository(
514+ self.context, code_import.git_repository)
515+ self.request.response.addInfoNotification(
516+ 'Code import created and repository set as default.')
517+ else:
518+ raise UnexpectedFormData(git_repository_type)
519+
520+ if branch_type == LINK_LP:
521 branch_location = data.get('branch_location')
522 if branch_location != self.series.branch:
523 self.series.branch = branch_location
524 # Request an initial upload of translation files.
525 getUtility(IRosettaUploadJobSource).create(
526 self.series.branch, NULL_REVISION)
527- else:
528- self.series.branch = branch_location
529- self.add_update_notification()
530- else:
531+ self.add_update_notification()
532+ elif branch_type == IMPORT_EXTERNAL:
533 branch_name = data.get('branch_name')
534 branch_owner = data.get('branch_owner')
535-
536- if branch_type == IMPORT_EXTERNAL:
537- rcs_type = data.get('rcs_type')
538- if rcs_type == RevisionControlSystems.CVS:
539- cvs_root = data.get('repo_url')
540- cvs_module = data.get('cvs_module')
541- url = None
542- else:
543- cvs_root = None
544- cvs_module = None
545- url = data.get('repo_url')
546- rcs_item = RevisionControlSystems.items[rcs_type.name]
547- try:
548- code_import = getUtility(ICodeImportSet).new(
549- owner=branch_owner,
550- registrant=self.user,
551- context=self.context,
552- branch_name=branch_name,
553- rcs_type=rcs_item,
554- target_rcs_type=TargetRevisionControlSystems.BZR,
555- url=url,
556- cvs_root=cvs_root,
557- cvs_module=cvs_module)
558- except BranchExists as e:
559- self._setBranchExists(e.existing_branch, 'branch_name')
560- self.abort_update()
561- return
562- self.series.branch = code_import.branch
563- self.request.response.addInfoNotification(
564- 'Code import created and branch linked to the series.')
565+ rcs_type = data.get('rcs_type')
566+ if rcs_type == RevisionControlSystems.CVS:
567+ cvs_root = data.get('repo_url')
568+ cvs_module = data.get('cvs_module')
569+ url = None
570 else:
571- raise UnexpectedFormData(branch_type)
572+ cvs_root = None
573+ cvs_module = None
574+ url = data.get('repo_url')
575+ rcs_item = RevisionControlSystems.items[rcs_type.name]
576+ try:
577+ code_import = getUtility(ICodeImportSet).new(
578+ owner=branch_owner,
579+ registrant=self.user,
580+ context=self.context,
581+ branch_name=branch_name,
582+ rcs_type=rcs_item,
583+ target_rcs_type=TargetRevisionControlSystems.BZR,
584+ url=url,
585+ cvs_root=cvs_root,
586+ cvs_module=cvs_module)
587+ except BranchExists as e:
588+ self._setBranchExists(e.existing_branch, 'branch_name')
589+ self.abort_update()
590+ return
591+ self.series.branch = code_import.branch
592+ self.request.response.addInfoNotification(
593+ 'Code import created and branch linked to the series.')
594+ else:
595+ raise UnexpectedFormData(branch_type)
596
597
598 class ProductRdfView(BaseRdfView):
599
600=== modified file 'lib/lp/registry/browser/tests/productseries-setbranch-view.txt'
601--- lib/lp/registry/browser/tests/productseries-setbranch-view.txt 2016-10-15 13:41:19 +0000
602+++ lib/lp/registry/browser/tests/productseries-setbranch-view.txt 2016-11-11 14:59:36 +0000
603@@ -39,7 +39,7 @@
604 must be provided.
605
606 >>> form = {
607- ... 'field.branch_type': 'link-lp-bzr',
608+ ... 'field.branch_type': 'link-lp',
609 ... 'field.actions.update': 'Update',
610 ... }
611 >>> view = create_initialized_view(
612@@ -52,7 +52,7 @@
613 validation error.
614
615 >>> form = {
616- ... 'field.branch_type': 'link-lp-bzr',
617+ ... 'field.branch_type': 'link-lp',
618 ... 'field.branch_location': 'foo',
619 ... 'field.actions.update': 'Update',
620 ... }
621@@ -69,7 +69,7 @@
622 >>> branch = factory.makeBranch(
623 ... name='impala-branch', owner=driver, product=product)
624 >>> form = {
625- ... 'field.branch_type': 'link-lp-bzr',
626+ ... 'field.branch_type': 'link-lp',
627 ... 'field.branch_location': branch.unique_name,
628 ... 'field.actions.update': 'Update',
629 ... }
630
631=== modified file 'lib/lp/registry/browser/tests/test_product.py'
632--- lib/lp/registry/browser/tests/test_product.py 2016-09-19 11:47:33 +0000
633+++ lib/lp/registry/browser/tests/test_product.py 2016-11-11 14:59:36 +0000
634@@ -30,7 +30,10 @@
635 InformationType,
636 ServiceUsage,
637 )
638+from lp.code.enums import RevisionControlSystems
639+from lp.code.interfaces.codeimport import CODE_IMPORT_GIT_TARGET_FEATURE_FLAG
640 from lp.code.interfaces.gitrepository import IGitRepositorySet
641+from lp.code.tests.helpers import GitHostingFixture
642 from lp.registry.browser.product import (
643 ProjectAddStepOne,
644 ProjectAddStepTwo,
645@@ -47,6 +50,7 @@
646 from lp.registry.model.product import Product
647 from lp.services.config import config
648 from lp.services.database.interfaces import IStore
649+from lp.services.features.testing import FeatureFixture
650 from lp.services.webapp.publisher import canonical_url
651 from lp.services.webapp.vhosts import allvhosts
652 from lp.testing import (
653@@ -914,7 +918,7 @@
654 # control defaults to empty.
655 project = self.factory.makeProduct()
656 browser = self.getBrowser(project, '+configure-code')
657- self.assertEqual('', browser.getControl('Git repository').value)
658+ self.assertEqual('', browser.getControl('Git repository:').value)
659
660 def test_initial_git_repository(self):
661 # If a project has a default Git repository, its "Git repository"
662@@ -926,14 +930,16 @@
663 unique_name = repo.unique_name
664 browser = self.getBrowser(project, '+configure-code')
665 self.assertEqual(
666- unique_name, browser.getControl('Git repository').value)
667+ unique_name, browser.getControl('Git repository:').value)
668
669 def test_link_existing_git_repository(self):
670 repo = removeSecurityProxy(self.factory.makeGitRepository(
671 target=self.factory.makeProduct()))
672 browser = self.getBrowser(repo.project, '+configure-code')
673 browser.getControl('Git', index=0).click()
674- browser.getControl('Git repository').value = repo.shortened_path
675+ self.assertTrue(browser.getControl(
676+ 'Link to a Git repository already on Launchpad').selected)
677+ browser.getControl('Git repository:').value = repo.shortened_path
678 browser.getControl('Update').click()
679
680 tag = Tag(
681@@ -941,6 +947,64 @@
682 text='Project settings updated.')
683 self.assertThat(browser.contents, HTMLContains(tag))
684
685+ def test_import_git_repository_requires_feature_flag(self):
686+ project = self.factory.makeProduct()
687+ browser = self.getBrowser(project, '+configure-code')
688+ self.assertRaises(
689+ LookupError, browser.getControl,
690+ 'Import a Git repository hosted somewhere else')
691+
692+ def test_import_git_repository(self):
693+ self.useFixture(
694+ FeatureFixture({CODE_IMPORT_GIT_TARGET_FEATURE_FLAG: u'on'}))
695+ self.useFixture(GitHostingFixture())
696+ owner = self.factory.makePerson()
697+ project = self.factory.makeProduct(owner=owner)
698+ browser = self.getBrowser(project, '+configure-code')
699+ browser.getControl('Git', index=0).click()
700+ browser.getControl(
701+ 'Import a Git repository hosted somewhere else').click()
702+ browser.getControl('Git repository name').value = 'imported'
703+ browser.getControl('Git repository URL').value = (
704+ 'https://git.example.org/imported')
705+ browser.getControl('Update').click()
706+
707+ tag = Tag(
708+ 'success-div', 'div', attrs={'class': 'informational message'},
709+ text='Code import created and repository set as default.')
710+ self.assertThat(browser.contents, HTMLContains(tag))
711+ login_person(owner)
712+ repo = getUtility(IGitRepositorySet).getDefaultRepository(project)
713+ self.assertIsNotNone(repo.code_import)
714+ self.assertEqual(RevisionControlSystems.GIT, repo.code_import.rcs_type)
715+ self.assertEqual(
716+ 'https://git.example.org/imported', repo.code_import.url)
717+
718+ def test_import_git_repository_bad_scheme(self):
719+ self.useFixture(
720+ FeatureFixture({CODE_IMPORT_GIT_TARGET_FEATURE_FLAG: u'on'}))
721+ owner = self.factory.makePerson()
722+ project = self.factory.makeProduct(owner=owner)
723+ browser = self.getBrowser(project, '+configure-code')
724+ browser.getControl('Git', index=0).click()
725+ browser.getControl(
726+ 'Import a Git repository hosted somewhere else').click()
727+ browser.getControl('Git repository name').value = 'imported'
728+ browser.getControl('Git repository URL').value = (
729+ 'svn://svn.example.org/imported')
730+ browser.getControl('Update').click()
731+
732+ tag = Tag(
733+ 'error', 'div', attrs={'class': 'message'},
734+ text=(
735+ 'The URI scheme &quot;svn&quot; is not allowed. '
736+ 'Only URIs with the following schemes may be used: '
737+ 'git, http, https'))
738+ self.assertThat(browser.contents, HTMLContains(tag))
739+ login_person(owner)
740+ self.assertIsNone(
741+ getUtility(IGitRepositorySet).getDefaultRepository(project))
742+
743 def test_editsshkeys_link_if_no_keys_registered(self):
744 project = self.factory.makeProduct()
745 browser = self.getBrowser(project, '+configure-code')