Merge lp:~cjwatson/launchpad/git-recipe-browser-existing into lp:launchpad

Proposed by Colin Watson on 2016-01-12
Status: Merged
Merged at revision: 17897
Proposed branch: lp:~cjwatson/launchpad/git-recipe-browser-existing
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/git-recipe-stale
Diff against target: 1027 lines (+316/-189)
4 files modified
lib/lp/app/widgets/suggestion.py (+2/-2)
lib/lp/code/browser/sourcepackagerecipe.py (+24/-16)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+285/-166)
lib/lp/code/templates/sourcepackagerecipe-index.pt (+5/-5)
To merge this branch: bzr merge lp:~cjwatson/launchpad/git-recipe-browser-existing
Reviewer Review Type Date Requested Status
William Grant code 2016-01-12 Approve on 2016-01-14
Review via email: mp+282297@code.launchpad.net

Commit message

Allow editing existing Git recipes.

Description of the change

Allow editing existing Git recipes.

I plan to put the browser code to create new recipes in place more or less last; adding browser code for existing recipes gives me the opportunity to put the bulk of the test code in place.

To post a comment you must log in.
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/app/widgets/suggestion.py'
2--- lib/lp/app/widgets/suggestion.py 2015-10-26 14:54:43 +0000
3+++ lib/lp/app/widgets/suggestion.py 2016-01-14 20:31:44 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Widgets related to IBranch."""
10@@ -355,7 +355,7 @@
11 class RecipeOwnerWidget(SuggestionWidget):
12 """Widget for selecting a recipe owner.
13
14- The current user and the base branch owner are suggested.
15+ The current user and the base source owner are suggested.
16 """
17
18 @staticmethod
19
20=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
21--- lib/lp/code/browser/sourcepackagerecipe.py 2016-01-11 21:11:27 +0000
22+++ lib/lp/code/browser/sourcepackagerecipe.py 2016-01-14 20:31:44 +0000
23@@ -86,9 +86,12 @@
24 from lp.code.errors import (
25 BuildAlreadyPending,
26 NoSuchBranch,
27+ NoSuchGitRepository,
28 PrivateBranchRecipe,
29+ PrivateGitRepositoryRecipe,
30 TooNewRecipeFormat,
31 )
32+from lp.code.interfaces.branch import IBranch
33 from lp.code.interfaces.branchtarget import IBranchTarget
34 from lp.code.interfaces.sourcepackagerecipe import (
35 IRecipeBranchSource,
36@@ -627,12 +630,17 @@
37 except ForbiddenInstructionError as e:
38 self.setFieldError(
39 'recipe_text',
40- 'The bzr-builder instruction "%s" is not permitted '
41- 'here.' % e.instruction_name)
42+ 'The recipe instruction "%s" is not permitted here.' %
43+ e.instruction_name)
44 except NoSuchBranch as e:
45 self.setFieldError(
46 'recipe_text', '%s is not a branch on Launchpad.' % e.name)
47- except (RecipeParseError, PrivateBranchRecipe) as e:
48+ except NoSuchGitRepository as e:
49+ self.setFieldError(
50+ 'recipe_text',
51+ '%s is not a Git repository on Launchpad.' % e.name)
52+ except (RecipeParseError, PrivateBranchRecipe,
53+ PrivateGitRepositoryRecipe) as e:
54 self.setFieldError('recipe_text', str(e))
55 raise ErrorHandled()
56
57@@ -676,24 +684,24 @@
58 super(RecipeRelatedBranchesMixin, self).setUpWidgets(context)
59 self.widgets['related-branches'].display_label = False
60 self.widgets['related-branches'].setRenderedValue(dict(
61- related_package_branch_info=self.related_package_branch_info,
62- related_series_branch_info=self.related_series_branch_info))
63+ related_package_branch_info=self.related_package_branch_info,
64+ related_series_branch_info=self.related_series_branch_info))
65
66 @cachedproperty
67 def related_series_branch_info(self):
68 branch_to_check = self.getBranch()
69- return IBranchTarget(
70- branch_to_check.target).getRelatedSeriesBranchInfo(
71- branch_to_check,
72- limit_results=5)
73+ if IBranch.providedBy(branch_to_check):
74+ branch_target = IBranchTarget(branch_to_check.target)
75+ return branch_target.getRelatedSeriesBranchInfo(
76+ branch_to_check, limit_results=5)
77
78 @cachedproperty
79 def related_package_branch_info(self):
80 branch_to_check = self.getBranch()
81- return IBranchTarget(
82- branch_to_check.target).getRelatedPackageBranchInfo(
83- branch_to_check,
84- limit_results=5)
85+ if IBranch.providedBy(branch_to_check):
86+ branch_target = IBranchTarget(branch_to_check.target)
87+ return branch_target.getRelatedPackageBranchInfo(
88+ branch_to_check, limit_results=5)
89
90
91 class SourcePackageRecipeAddView(RecipeRelatedBranchesMixin,
92@@ -822,8 +830,8 @@
93 """View for editing Source Package Recipes."""
94
95 def getBranch(self):
96- """The branch on which the recipe is built."""
97- return self.context.base_branch
98+ """The branch or repository on which the recipe is built."""
99+ return self.context.base
100
101 @property
102 def title(self):
103@@ -847,7 +855,7 @@
104 any_owner_choice = PersonChoice(
105 __name__='owner', title=owner_field.title,
106 description=(u"As an administrator you are able to assign"
107- u" this branch to any person or team."),
108+ u" this recipe to any person or team."),
109 required=True, vocabulary='ValidPersonOrTeam')
110 any_owner_field = form.Fields(
111 any_owner_choice, render_context=self.render_context)
112
113=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
114--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2016-01-11 21:11:27 +0000
115+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2016-01-14 20:31:44 +0000
116@@ -1,4 +1,4 @@
117-# Copyright 2010-2013 Canonical Ltd. This software is licensed under the
118+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
119 # GNU Affero General Public License version 3 (see the file LICENSE).
120
121 """Tests for the source package recipe view classes and templates."""
122@@ -10,6 +10,7 @@
123 datetime,
124 timedelta,
125 )
126+import re
127 from textwrap import dedent
128
129 from BeautifulSoup import BeautifulSoup
130@@ -34,7 +35,10 @@
131 from lp.code.browser.sourcepackagerecipebuild import (
132 SourcePackageRecipeBuildView,
133 )
134-from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT_BZR
135+from lp.code.interfaces.sourcepackagerecipe import (
136+ MINIMAL_RECIPE_TEXT_BZR,
137+ MINIMAL_RECIPE_TEXT_GIT,
138+ )
139 from lp.code.tests.helpers import recipe_parser_newest_version
140 from lp.registry.interfaces.person import TeamMembershipPolicy
141 from lp.registry.interfaces.pocket import PackagePublishingPocket
142@@ -89,35 +93,21 @@
143 canonical_url(recipe))
144
145
146-class TestCaseForRecipe(BrowserTestCase):
147- """Create some sample data for recipe tests."""
148-
149- def setUp(self):
150- """Provide useful defaults."""
151- super(TestCaseForRecipe, self).setUp()
152- self.chef = self.factory.makePerson(
153- displayname='Master Chef', name='chef')
154- self.user = self.chef
155- self.ppa = self.factory.makeArchive(
156- displayname='Secret PPA', owner=self.chef, name='ppa')
157- self.squirrel = self.factory.makeDistroSeries(
158- displayname='Secret Squirrel', name='secret', version='100.04',
159- distribution=self.ppa.distribution)
160- naked_squirrel = removeSecurityProxy(self.squirrel)
161- naked_squirrel.nominatedarchindep = self.squirrel.newArch(
162- 'i386', getUtility(IProcessorSet).getByName('386'), False,
163- self.chef)
164-
165- def makeRecipe(self):
166- """Create and return a specific recipe."""
167- chocolate = self.factory.makeProduct(name='chocolate')
168- cake_branch = self.factory.makeProductBranch(
169- owner=self.chef, name='cake', product=chocolate)
170- return self.factory.makeSourcePackageRecipe(
171- owner=self.chef, distroseries=self.squirrel, name=u'cake_recipe',
172- description=u'This recipe builds a foo for disto bar, with my'
173- ' Secret Squirrel changes.', branches=[cake_branch],
174- daily_build_archive=self.ppa)
175+class BzrMixin:
176+ """Mixin for Bazaar-based recipe tests."""
177+
178+ minimal_recipe_text = MINIMAL_RECIPE_TEXT_BZR
179+ branch_type = "branch"
180+ no_such_object_message = "is not a branch on Launchpad."
181+
182+ def makeBranch(self, target=None, **kwargs):
183+ return self.factory.makeAnyBranch(product=target, **kwargs)
184+
185+ def makePackageBranch(self, **kwargs):
186+ return self.factory.makePackageBranch(**kwargs)
187+
188+ def makeRelatedBranches(self, *args, **kwargs):
189+ return self.factory.makeRelatedBranches(*args, **kwargs)
190
191 def checkRelatedBranches(self, related_series_branch_info,
192 related_package_branch_info, browser_contents):
193@@ -194,6 +184,86 @@
194 expected_branch_info.append(product_series.name)
195 self.assertEqual(expected_branch_info, series_branches_info)
196
197+ @staticmethod
198+ def getRepository(branch):
199+ return branch
200+
201+ @staticmethod
202+ def getBranchRecipeText(branch):
203+ return branch.identity
204+
205+ @staticmethod
206+ def getMinimalRecipeText(branch):
207+ return MINIMAL_RECIPE_TEXT_BZR % branch.identity
208+
209+
210+class GitMixin:
211+ """Mixin for Git-based recipe tests."""
212+
213+ minimal_recipe_text = MINIMAL_RECIPE_TEXT_GIT
214+ branch_type = "repository"
215+ no_such_object_message = "is not a Git repository on Launchpad."
216+
217+ def makeBranch(self, **kwargs):
218+ return self.factory.makeGitRefs(**kwargs)[0]
219+
220+ def makePackageBranch(self, sourcepackagename=None, **kwargs):
221+ dsp = self.factory.makeDistributionSourcePackage(
222+ sourcepackagename=sourcepackagename)
223+ return self.factory.makeGitRefs(target=dsp, **kwargs)[0]
224+
225+ def makeRelatedBranches(self, reference_branch=None, *args, **kwargs):
226+ if reference_branch is None:
227+ [reference_branch] = self.factory.makeGitRefs()
228+ return reference_branch, [], []
229+
230+ def checkRelatedBranches(self, *args, **kwargs):
231+ pass
232+
233+ @staticmethod
234+ def getRepository(branch):
235+ return branch.repository
236+
237+ @staticmethod
238+ def getBranchRecipeText(branch):
239+ return "%s %s" % (branch.repository.identity, branch.name)
240+
241+ @staticmethod
242+ def getMinimalRecipeText(branch):
243+ return MINIMAL_RECIPE_TEXT_GIT % (
244+ branch.repository.identity, branch.name)
245+
246+
247+class TestCaseForRecipe(BrowserTestCase):
248+ """Create some sample data for recipe tests."""
249+
250+ def setUp(self):
251+ """Provide useful defaults."""
252+ super(TestCaseForRecipe, self).setUp()
253+ self.chef = self.factory.makePerson(
254+ displayname='Master Chef', name='chef')
255+ self.user = self.chef
256+ self.ppa = self.factory.makeArchive(
257+ displayname='Secret PPA', owner=self.chef, name='ppa')
258+ self.squirrel = self.factory.makeDistroSeries(
259+ displayname='Secret Squirrel', name='secret', version='100.04',
260+ distribution=self.ppa.distribution)
261+ naked_squirrel = removeSecurityProxy(self.squirrel)
262+ naked_squirrel.nominatedarchindep = self.squirrel.newArch(
263+ 'i386', getUtility(IProcessorSet).getByName('386'), False,
264+ self.chef)
265+
266+ def makeRecipe(self, **kwargs):
267+ """Create and return a specific recipe."""
268+ chocolate = self.factory.makeProduct(name='chocolate')
269+ cake_branch = self.makeBranch(
270+ owner=self.chef, name=u'cake', target=chocolate)
271+ return self.factory.makeSourcePackageRecipe(
272+ owner=self.chef, distroseries=self.squirrel, name=u'cake_recipe',
273+ description=u'This recipe builds a foo for distro bar, with my'
274+ ' Secret Squirrel changes.', branches=[cake_branch],
275+ daily_build_archive=self.ppa, **kwargs)
276+
277
278 class TestSourcePackageRecipeAddViewInitalValues(TestCaseWithFactory):
279
280@@ -272,7 +342,7 @@
281 self.assertEqual(set(), series.intersection(other_series))
282
283
284-class TestSourcePackageRecipeAddView(TestCaseForRecipe):
285+class TestSourcePackageRecipeAddView(BzrMixin, TestCaseForRecipe):
286
287 layer = DatabaseFunctionalLayer
288
289@@ -399,8 +469,7 @@
290 browser.getControl('Create Recipe').click()
291 self.assertEqual(
292 get_feedback_messages(browser.contents)[1],
293- html_escape(
294- 'The bzr-builder instruction "run" is not permitted here.'))
295+ html_escape('The recipe instruction "run" is not permitted here.'))
296
297 def createRecipe(self, recipe_text, branch=None):
298 if branch is None:
299@@ -730,7 +799,7 @@
300 self.assertIsNot(None, new_ppa)
301
302
303-class TestSourcePackageRecipeEditView(TestCaseForRecipe):
304+class TestSourcePackageRecipeEditViewMixin:
305 """Test the editing behaviour of a source package recipe."""
306
307 layer = DatabaseFunctionalLayer
308@@ -741,10 +810,10 @@
309 distribution=self.ppa.distribution)
310 product = self.factory.makeProduct(
311 name='ratatouille', displayname='Ratatouille')
312- veggie_branch = self.factory.makeBranch(
313- owner=self.chef, product=product, name='veggies')
314- meat_branch = self.factory.makeBranch(
315- owner=self.chef, product=product, name='meat')
316+ veggie_branch = self.makeBranch(
317+ owner=self.chef, target=product, name=u'veggies')
318+ meat_branch = self.makeBranch(
319+ owner=self.chef, target=product, name=u'meat')
320 recipe = self.factory.makeSourcePackageRecipe(
321 owner=self.chef, registrant=self.chef,
322 name=u'things', description=u'This is a recipe',
323@@ -754,14 +823,13 @@
324 distribution=self.ppa.distribution, name='ppa2',
325 displayname="PPA 2", owner=self.chef)
326
327- meat_path = meat_branch.bzr_identity
328+ recipe_text = self.getMinimalRecipeText(meat_branch)
329
330 browser = self.getUserBrowser(canonical_url(recipe), user=self.chef)
331 browser.getLink('Edit recipe').click()
332 browser.getControl(name='field.name').value = 'fings'
333 browser.getControl('Description').value = 'This is stuff'
334- browser.getControl('Recipe text').value = (
335- MINIMAL_RECIPE_TEXT_BZR % meat_path)
336+ browser.getControl('Recipe text').value = recipe_text
337 browser.getControl('Secret Squirrel').click()
338 browser.getControl('Mumbly Midget').click()
339 browser.getControl('PPA 2').click()
340@@ -771,10 +839,7 @@
341 self.assertThat(
342 'Edit This is stuff', MatchesTagText(content, 'edit-description'))
343 self.assertThat(
344- 'Edit '
345- '# bzr-builder format 0.3 deb-version {debupstream}-0~{revno}\n'
346- 'lp://dev/~chef/ratatouille/meat',
347- MatchesTagText(content, 'edit-recipe_text'))
348+ 'Edit ' + recipe_text, MatchesTagText(content, 'edit-recipe_text'))
349 self.assertThat(
350 'Distribution series: Edit Mumbly Midget',
351 MatchesTagText(content, 'distroseries'))
352@@ -784,8 +849,7 @@
353 def test_edit_recipe_sets_date_last_modified(self):
354 """Editing a recipe sets the date_last_modified property."""
355 date_created = datetime(2000, 1, 1, 12, tzinfo=UTC)
356- recipe = self.factory.makeSourcePackageRecipe(
357- owner=self.chef, date_created=date_created)
358+ recipe = self.makeRecipe(date_created=date_created)
359
360 login_person(self.chef)
361 view = SourcePackageRecipeEditView(recipe, LaunchpadTestRequest())
362@@ -803,17 +867,17 @@
363 distribution=self.ppa.distribution)
364 product = self.factory.makeProduct(
365 name='ratatouille', displayname='Ratatouille')
366- veggie_branch = self.factory.makeBranch(
367- owner=self.chef, product=product, name='veggies')
368- meat_branch = self.factory.makeBranch(
369- owner=self.chef, product=product, name='meat')
370+ veggie_branch = self.makeBranch(
371+ owner=self.chef, target=product, name=u'veggies')
372+ meat_branch = self.makeBranch(
373+ owner=self.chef, target=product, name=u'meat')
374 recipe = self.factory.makeSourcePackageRecipe(
375 owner=self.chef, registrant=self.chef,
376 name=u'things', description=u'This is a recipe',
377 distroseries=self.squirrel, branches=[veggie_branch],
378 daily_build_archive=self.ppa)
379
380- meat_path = meat_branch.bzr_identity
381+ recipe_text = self.getMinimalRecipeText(meat_branch)
382 expert = getUtility(ILaunchpadCelebrities).admin.teamowner
383
384 browser = self.getUserBrowser(canonical_url(recipe), user=expert)
385@@ -827,8 +891,7 @@
386
387 browser.getControl(name='field.name').value = 'fings'
388 browser.getControl('Description').value = 'This is stuff'
389- browser.getControl('Recipe text').value = (
390- MINIMAL_RECIPE_TEXT_BZR % meat_path)
391+ browser.getControl('Recipe text').value = recipe_text
392 browser.getControl('Secret Squirrel').click()
393 browser.getControl('Mumbly Midget').click()
394 browser.getControl('Update Recipe').click()
395@@ -838,10 +901,7 @@
396 self.assertThat(
397 'Edit This is stuff', MatchesTagText(content, 'edit-description'))
398 self.assertThat(
399- 'Edit '
400- '# bzr-builder format 0.3 deb-version {debupstream}-0~{revno}\n'
401- 'lp://dev/~chef/ratatouille/meat',
402- MatchesTagText(content, 'edit-recipe_text'))
403+ 'Edit ' + recipe_text, MatchesTagText(content, 'edit-recipe_text'))
404 self.assertThat(
405 'Distribution series: Edit Mumbly Midget',
406 MatchesTagText(content, 'distroseries'))
407@@ -852,8 +912,8 @@
408 distribution=self.ppa.distribution)
409 product = self.factory.makeProduct(
410 name='ratatouille', displayname='Ratatouille')
411- veggie_branch = self.factory.makeBranch(
412- owner=self.chef, product=product, name='veggies')
413+ veggie_branch = self.makeBranch(
414+ owner=self.chef, target=product, name=u'veggies')
415 recipe = self.factory.makeSourcePackageRecipe(
416 owner=self.chef, registrant=self.chef,
417 name=u'things', description=u'This is a recipe',
418@@ -867,8 +927,7 @@
419
420 self.assertEqual(
421 get_feedback_messages(browser.contents)[1],
422- html_escape(
423- 'The bzr-builder instruction "run" is not permitted here.'))
424+ html_escape('The recipe instruction "run" is not permitted here.'))
425
426 def test_edit_recipe_format_too_new(self):
427 # If the recipe's format version is too new, we should notify the
428@@ -878,17 +937,16 @@
429 distribution=self.ppa.distribution)
430 product = self.factory.makeProduct(
431 name='ratatouille', displayname='Ratatouille')
432- veggie_branch = self.factory.makeBranch(
433- owner=self.chef, product=product, name='veggies')
434+ veggie_branch = self.makeBranch(
435+ owner=self.chef, target=product, name=u'veggies')
436 recipe = self.factory.makeSourcePackageRecipe(
437 owner=self.chef, registrant=self.chef,
438 name=u'things', description=u'This is a recipe',
439 distroseries=self.squirrel, branches=[veggie_branch])
440
441- new_recipe_text = dedent(u'''\
442- # bzr-builder format 145.115 deb-version {debupstream}-0~{revno}
443- %s
444- ''') % recipe.base_branch.bzr_identity
445+ new_recipe_text = re.sub(
446+ 'format [^ ]*', 'format 145.115',
447+ self.getMinimalRecipeText(veggie_branch))
448
449 with recipe_parser_newest_version(145.115):
450 browser = self.getViewBrowser(recipe)
451@@ -906,10 +964,10 @@
452 distribution=self.ppa.distribution)
453 product = self.factory.makeProduct(
454 name='ratatouille', displayname='Ratatouille')
455- veggie_branch = self.factory.makeBranch(
456- owner=self.chef, product=product, name='veggies')
457- meat_branch = self.factory.makeBranch(
458- owner=self.chef, product=product, name='meat')
459+ veggie_branch = self.makeBranch(
460+ owner=self.chef, target=product, name=u'veggies')
461+ meat_branch = self.makeBranch(
462+ owner=self.chef, target=product, name=u'meat')
463 recipe = self.factory.makeSourcePackageRecipe(
464 owner=self.chef, registrant=self.chef,
465 name=u'things', description=u'This is a recipe',
466@@ -919,14 +977,13 @@
467 name=u'fings', description=u'This is a recipe',
468 distroseries=self.squirrel, branches=[veggie_branch])
469
470- meat_path = meat_branch.bzr_identity
471+ recipe_text = self.getMinimalRecipeText(meat_branch)
472
473 browser = self.getUserBrowser(canonical_url(recipe), user=self.chef)
474 browser.getLink('Edit recipe').click()
475 browser.getControl(name='field.name').value = 'fings'
476 browser.getControl('Description').value = 'This is stuff'
477- browser.getControl('Recipe text').value = (
478- MINIMAL_RECIPE_TEXT_BZR % meat_path)
479+ browser.getControl('Recipe text').value = recipe_text
480 browser.getControl('Secret Squirrel').click()
481 browser.getControl('Mumbly Midget').click()
482 browser.getControl('Update Recipe').click()
483@@ -939,30 +996,32 @@
484 # If a user tries to set source package recipe to use a private
485 # branch, they should get an error.
486 recipe = self.factory.makeSourcePackageRecipe(owner=self.user)
487- branch = self.factory.makeAnyBranch(
488+ branch = self.makeBranch(
489 owner=self.user, information_type=InformationType.USERDATA)
490 with person_logged_in(self.user):
491- bzr_identity = branch.bzr_identity
492- recipe_text = MINIMAL_RECIPE_TEXT_BZR % bzr_identity
493+ identity = self.getRepository(branch).identity
494+ recipe_text = self.getMinimalRecipeText(branch)
495 browser = self.getViewBrowser(recipe, '+edit')
496 browser.getControl('Recipe text').value = recipe_text
497 browser.getControl('Update Recipe').click()
498 self.assertEqual(
499 get_feedback_messages(browser.contents)[1],
500- 'Recipe may not refer to private branch: %s' % bzr_identity)
501+ 'Recipe may not refer to private %s: %s' % (
502+ self.branch_type, identity))
503
504 def test_edit_recipe_no_branch(self):
505 # If a user tries to set a source package recipe to use a branch
506- # that isn't registred, they will get an error.
507- recipe = self.factory.makeSourcePackageRecipe(owner=self.user)
508- no_branch_recipe_text = recipe.recipe_text[:-4]
509- expected_name = recipe.base_branch.unique_name[:-3]
510+ # that isn't registered, they will get an error.
511+ recipe = self.factory.makeSourcePackageRecipe(
512+ owner=self.user, branches=[self.makeBranch()])
513+ no_branch_recipe_text = (
514+ recipe.recipe_text.splitlines()[0] + "\nlp:nonexistent\n")
515 browser = self.getViewBrowser(recipe, '+edit')
516 browser.getControl('Recipe text').value = no_branch_recipe_text
517 browser.getControl('Update Recipe').click()
518 self.assertEqual(
519 get_feedback_messages(browser.contents)[1],
520- 'lp://dev/%s is not a branch on Launchpad.' % expected_name)
521+ 'lp:nonexistent %s' % self.no_such_object_message)
522
523 def _test_edit_recipe_with_no_related_branches(self, recipe):
524 # The Related Branches section should not appear if there are no
525@@ -978,26 +1037,31 @@
526 def test_edit_product_branch_with_no_related_branches_recipe(self):
527 # The Related Branches section should not appear if there are no
528 # related branches.
529- base_branch = self.factory.makeBranch()
530+ base_branch = self.makeBranch()
531 recipe = self.factory.makeSourcePackageRecipe(
532- owner=self.chef, branches=[base_branch])
533+ owner=self.chef, branches=[base_branch])
534 self._test_edit_recipe_with_no_related_branches(recipe)
535
536 def test_edit_sourcepackage_branch_with_no_related_branches_recipe(self):
537 # The Related Branches section should not appear if there are no
538 # related branches.
539- base_branch = self.factory.makePackageBranch()
540+ base_branch = self.makePackageBranch()
541 recipe = self.factory.makeSourcePackageRecipe(
542- owner=self.chef, branches=[base_branch])
543+ owner=self.chef, branches=[base_branch])
544 self._test_edit_recipe_with_no_related_branches(recipe)
545
546+
547+class TestSourcePackageRecipeEditViewBzr(
548+ TestSourcePackageRecipeEditViewMixin, BzrMixin, TestCaseForRecipe):
549+
550 def test_edit_recipe_with_package_branches(self):
551 # The series branches table should not appear if there are none.
552 with person_logged_in(self.chef):
553- recipe = self.factory.makeSourcePackageRecipe(owner=self.chef)
554- self.factory.makeRelatedBranches(
555- reference_branch=recipe.base_branch,
556- with_series_branches=False)
557+ base_branch = self.makeBranch()
558+ recipe = self.factory.makeSourcePackageRecipe(
559+ owner=self.chef, branches=[base_branch])
560+ self.makeRelatedBranches(
561+ reference_branch=base_branch, with_series_branches=False)
562 browser = self.getUserBrowser(canonical_url(recipe), user=self.chef)
563 browser.getLink('Edit recipe').click()
564 soup = BeautifulSoup(browser.contents)
565@@ -1013,10 +1077,11 @@
566 def test_edit_recipe_with_series_branches(self):
567 # The package branches table should not appear if there are none.
568 with person_logged_in(self.chef):
569- recipe = self.factory.makeSourcePackageRecipe(owner=self.chef)
570- self.factory.makeRelatedBranches(
571- reference_branch=recipe.base_branch,
572- with_package_branches=False)
573+ base_branch = self.makeBranch()
574+ recipe = self.factory.makeSourcePackageRecipe(
575+ owner=self.chef, branches=[base_branch])
576+ self.makeRelatedBranches(
577+ reference_branch=base_branch, with_package_branches=False)
578 browser = self.getUserBrowser(canonical_url(recipe), user=self.chef)
579 browser.getLink('Edit recipe').click()
580 soup = BeautifulSoup(browser.contents)
581@@ -1032,11 +1097,11 @@
582 def test_edit_product_branch_recipe_with_related_branches(self):
583 # The related branches should be rendered correctly on the page.
584 with person_logged_in(self.chef):
585- recipe = self.factory.makeSourcePackageRecipe(owner=self.chef)
586- (branch, related_series_branch_info,
587- related_package_branch_info) = (
588- self.factory.makeRelatedBranches(
589- reference_branch=recipe.base_branch))
590+ base_branch = self.makeBranch()
591+ recipe = self.factory.makeSourcePackageRecipe(
592+ owner=self.chef, branches=[base_branch])
593+ _, related_series_branch_info, related_package_branch_info = (
594+ self.makeRelatedBranches(reference_branch=base_branch))
595 browser = self.getUserBrowser(
596 canonical_url(recipe, view_name='+edit'), user=self.chef)
597 self.checkRelatedBranches(
598@@ -1046,18 +1111,23 @@
599 def test_edit_sourcepackage_branch_recipe_with_related_branches(self):
600 # The related branches should be rendered correctly on the page.
601 with person_logged_in(self.chef):
602- reference_branch = self.factory.makePackageBranch()
603+ reference_branch = self.makePackageBranch()
604 recipe = self.factory.makeSourcePackageRecipe(
605 owner=self.chef, branches=[reference_branch])
606- (branch, ignore, related_package_branch_info) = (
607- self.factory.makeRelatedBranches(reference_branch))
608- browser = self.getUserBrowser(
609- canonical_url(recipe, view_name='+edit'), user=self.chef)
610- self.checkRelatedBranches(
611- set(), related_package_branch_info, browser.contents)
612-
613-
614-class TestSourcePackageRecipeView(TestCaseForRecipe):
615+ _, _, related_package_branch_info = (
616+ self.makeRelatedBranches(reference_branch))
617+ browser = self.getUserBrowser(
618+ canonical_url(recipe, view_name='+edit'), user=self.chef)
619+ self.checkRelatedBranches(
620+ set(), related_package_branch_info, browser.contents)
621+
622+
623+class TestSourcePackageRecipeEditViewGit(
624+ TestSourcePackageRecipeEditViewMixin, GitMixin, TestCaseForRecipe):
625+ pass
626+
627+
628+class TestSourcePackageRecipeViewMixin:
629
630 layer = LaunchpadFunctionalLayer
631
632@@ -1086,8 +1156,8 @@
633 Recipe information
634 Build schedule: .* Built on request Edit
635 Owner: Master Chef Edit
636- Base branch: lp://dev/~chef/chocolate/cake
637- Debian version: {debupstream}-0~{revno}
638+ Base source: lp:.*~chef/chocolate.*cake
639+ Debian version: {debupstream}-0~{rev.*}
640 Daily build archive: Secret PPA Edit
641 Distribution series: Edit Secret Squirrel
642
643@@ -1097,8 +1167,8 @@
644 Request build\(s\)
645
646 Recipe contents Edit
647- # bzr-builder format 0.3 deb-version {debupstream}-0~{revno}
648- lp://dev/~chef/chocolate/cake""", self.getMainText(build.recipe))
649+ # .* format .* deb-version {debupstream}-0~{rev.*}
650+ lp:.*~chef/chocolate.*cake.*""", self.getMainText(build.recipe))
651
652 def test_index_success_with_buildlog(self):
653 # The buildlog is shown if it is there.
654@@ -1262,9 +1332,7 @@
655 [build6, build5, build4, build3, build2], view.builds)
656
657 def test_request_builds_redirects_on_get(self):
658- recipe = self.factory.makeSourcePackageRecipe(
659- owner=self.chef, daily_build_archive=self.ppa,
660- is_stale=True, build_daily=True)
661+ recipe = self.makeRecipe(is_stale=True, build_daily=True)
662 with person_logged_in(self.chef):
663 url = canonical_url(recipe)
664 browser = self.getViewBrowser(recipe, '+request-daily-build')
665@@ -1272,9 +1340,7 @@
666
667 def test_request_daily_builds_button_stale(self):
668 # Recipes that are stale and are built daily have a build now link
669- recipe = self.factory.makeSourcePackageRecipe(
670- owner=self.chef, daily_build_archive=self.ppa,
671- is_stale=True, build_daily=True)
672+ recipe = self.makeRecipe(is_stale=True, build_daily=True)
673 browser = self.getViewBrowser(recipe)
674 build_button = find_tag_by_id(browser.contents, 'field.actions.build')
675 self.assertIsNot(None, build_button)
676@@ -1282,9 +1348,7 @@
677 def test_request_daily_builds_button_not_stale(self):
678 # Recipes that are not stale do not have a build now link
679 login(ANONYMOUS)
680- recipe = self.factory.makeSourcePackageRecipe(
681- owner=self.chef, daily_build_archive=self.ppa,
682- is_stale=False, build_daily=True)
683+ recipe = self.makeRecipe(is_stale=False, build_daily=True)
684 browser = self.getViewBrowser(recipe)
685 build_button = find_tag_by_id(browser.contents, 'field.actions.build')
686 self.assertIs(None, build_button)
687@@ -1292,9 +1356,7 @@
688 def test_request_daily_builds_button_not_daily(self):
689 # Recipes that are not built daily do not have a build now link
690 login(ANONYMOUS)
691- recipe = self.factory.makeSourcePackageRecipe(
692- owner=self.chef, daily_build_archive=self.ppa,
693- is_stale=True, build_daily=False)
694+ recipe = self.makeRecipe(is_stale=True, build_daily=False)
695 browser = self.getViewBrowser(recipe)
696 build_button = find_tag_by_id(browser.contents, 'field.actions.build')
697 self.assertIs(None, build_button)
698@@ -1302,8 +1364,10 @@
699 def test_request_daily_builds_button_no_daily_ppa(self):
700 # Recipes that have no daily build ppa do not have a build now link
701 login(ANONYMOUS)
702+ branch = self.makeBranch()
703 recipe = self.factory.makeSourcePackageRecipe(
704- owner=self.chef, is_stale=True, build_daily=True)
705+ owner=self.chef, branches=[branch],
706+ is_stale=True, build_daily=True)
707 naked_recipe = removeSecurityProxy(recipe)
708 naked_recipe.daily_build_archive = None
709 browser = self.getViewBrowser(recipe)
710@@ -1314,8 +1378,7 @@
711 # Recipes do not have a build now link if the user does not have edit
712 # permission on the recipe.
713 login(ANONYMOUS)
714- recipe = self.factory.makeSourcePackageRecipe(
715- owner=self.chef, is_stale=True, build_daily=True)
716+ recipe = self.makeRecipe(is_stale=True, build_daily=True)
717 person = self.factory.makePerson()
718 browser = self.getViewBrowser(recipe, user=person)
719 build_button = find_tag_by_id(browser.contents, 'field.actions.build')
720@@ -1327,10 +1390,12 @@
721 login(ANONYMOUS)
722 distroseries = self.factory.makeSourcePackageRecipeDistroseries()
723 person = self.factory.makePerson()
724+ branch = self.makeBranch()
725 daily_build_archive = self.factory.makeArchive(
726- distribution=distroseries.distribution, owner=person)
727+ distribution=distroseries.distribution, owner=person)
728 recipe = self.factory.makeSourcePackageRecipe(
729- owner=self.chef, daily_build_archive=daily_build_archive,
730+ owner=self.chef, branches=[branch],
731+ daily_build_archive=daily_build_archive,
732 is_stale=True, build_daily=True)
733 browser = self.getViewBrowser(recipe)
734 build_button = find_tag_by_id(browser.contents, 'field.actions.build')
735@@ -1340,12 +1405,14 @@
736 # Recipes whose daily build ppa is disabled do not have a build now
737 # link.
738 distroseries = self.factory.makeSourcePackageRecipeDistroseries()
739+ branch = self.makeBranch()
740 daily_build_archive = self.factory.makeArchive(
741- distribution=distroseries.distribution, owner=self.user)
742+ distribution=distroseries.distribution, owner=self.user)
743 with person_logged_in(self.user):
744 daily_build_archive.disable()
745 recipe = self.factory.makeSourcePackageRecipe(
746- owner=self.chef, daily_build_archive=daily_build_archive,
747+ owner=self.chef, branches=[branch],
748+ daily_build_archive=daily_build_archive,
749 is_stale=True, build_daily=True)
750 browser = self.getViewBrowser(recipe)
751 build_button = find_tag_by_id(browser.contents, 'field.actions.build')
752@@ -1353,17 +1420,16 @@
753
754 def test_request_daily_builds_ajax_link_not_rendered(self):
755 # The Build now link should not be rendered without javascript.
756- recipe = self.factory.makeSourcePackageRecipe(
757- owner=self.chef, daily_build_archive=self.ppa,
758- is_stale=True, build_daily=True)
759+ recipe = self.makeRecipe(is_stale=True, build_daily=True)
760 browser = self.getViewBrowser(recipe)
761 build_link = find_tag_by_id(browser.contents, 'request-daily-builds')
762 self.assertIs(None, build_link)
763
764 def test_request_daily_builds_action(self):
765 # Daily builds should be triggered when requested.
766+ branch = self.makeBranch()
767 recipe = self.factory.makeSourcePackageRecipe(
768- owner=self.chef, daily_build_archive=self.ppa,
769+ owner=self.chef, branches=[branch], daily_build_archive=self.ppa,
770 is_stale=True, build_daily=True)
771 browser = self.getViewBrowser(recipe)
772 browser.getControl('Build now').click()
773@@ -1380,9 +1446,7 @@
774
775 def test_request_daily_builds_disabled_archive(self):
776 # Requesting a daily build from a disabled archive is a user error.
777- recipe = self.factory.makeSourcePackageRecipe(
778- owner=self.chef, daily_build_archive=self.ppa,
779- name=u'julia', is_stale=True, build_daily=True)
780+ recipe = self.makeRecipe(is_stale=True, build_daily=True)
781 harness = LaunchpadFormHarness(
782 recipe, SourcePackageRecipeRequestDailyBuildView)
783 with person_logged_in(self.ppa.owner):
784@@ -1394,9 +1458,7 @@
785
786 def test_request_daily_builds_obsolete_series(self):
787 # Requesting a daily build with an obsolete series gives a warning.
788- recipe = self.factory.makeSourcePackageRecipe(
789- owner=self.chef, daily_build_archive=self.ppa,
790- name=u'julia', is_stale=True, build_daily=True)
791+ recipe = self.makeRecipe(is_stale=True, build_daily=True)
792 warty = self.factory.makeSourcePackageRecipeDistroseries()
793 hoary = self.factory.makeSourcePackageRecipeDistroseries(name='hoary')
794 with person_logged_in(self.chef):
795@@ -1457,21 +1519,26 @@
796 Unauthorized, browser.getLink('Request build(s)').click)
797
798 def test_request_builds_archive_no_daily_build_archive(self):
799- recipe = self.factory.makeSourcePackageRecipe()
800+ branch = self.makeBranch()
801+ recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
802 view = SourcePackageRecipeRequestBuildsView(recipe, None)
803 self.assertIs(None, view.initial_values.get('archive'))
804
805 def test_request_builds_archive_daily_build_archive_unuploadable(self):
806+ branch = self.makeBranch()
807 ppa = self.factory.makeArchive()
808- recipe = self.factory.makeSourcePackageRecipe(daily_build_archive=ppa)
809+ recipe = self.factory.makeSourcePackageRecipe(
810+ branches=[branch], daily_build_archive=ppa)
811 with person_logged_in(self.chef):
812 view = SourcePackageRecipeRequestBuildsView(recipe, None)
813 self.assertIs(None, view.initial_values.get('archive'))
814
815 def test_request_builds_archive(self):
816+ branch = self.makeBranch()
817 ppa = self.factory.makeArchive(
818 displayname='Secret PPA', owner=self.chef, name='ppa2')
819- recipe = self.factory.makeSourcePackageRecipe(daily_build_archive=ppa)
820+ recipe = self.factory.makeSourcePackageRecipe(
821+ branches=[branch], daily_build_archive=ppa)
822 with person_logged_in(self.chef):
823 view = SourcePackageRecipeRequestBuildsView(recipe, None)
824 self.assertEqual(ppa, view.initial_values.get('archive'))
825@@ -1506,11 +1573,12 @@
826 # that owns the PPA.
827 registrant = self.factory.makePerson()
828 owner_team = self.factory.makeTeam(members=[registrant], name='team1')
829+ branch = self.makeBranch()
830 ppa_team = self.factory.makeTeam(members=[registrant], name='team2')
831 ppa = self.factory.makeArchive(owner=ppa_team, name='ppa')
832 return self.factory.makeSourcePackageRecipe(
833- registrant=registrant, owner=owner_team, daily_build_archive=ppa,
834- build_daily=True)
835+ registrant=registrant, owner=owner_team, branches=[branch],
836+ daily_build_archive=ppa, build_daily=True)
837
838 def test_owner_with_no_ppa_upload_permission(self):
839 # Daily build with upload issues are a problem.
840@@ -1541,7 +1609,9 @@
841 # When a PPA is disabled, it is only viewable to the owner. This
842 # case is handled with the view not showing builds into a disabled
843 # archive, rather than giving an Unauthorized error to the user.
844- recipe = self.factory.makeSourcePackageRecipe(build_daily=True)
845+ branch = self.makeBranch()
846+ recipe = self.factory.makeSourcePackageRecipe(
847+ branches=[branch], build_daily=True)
848 recipe.requestBuild(
849 recipe.daily_build_archive, recipe.owner, self.squirrel,
850 PackagePublishingPocket.RELEASE)
851@@ -1553,23 +1623,34 @@
852 extract_text(find_main_content(browser.contents)))
853
854
855-class TestSourcePackageRecipeBuildView(BrowserTestCase):
856+class TestSourcePackageRecipeViewBzr(
857+ TestSourcePackageRecipeViewMixin, BzrMixin, TestCaseForRecipe):
858+ pass
859+
860+
861+class TestSourcePackageRecipeViewGit(
862+ TestSourcePackageRecipeViewMixin, GitMixin, TestCaseForRecipe):
863+ pass
864+
865+
866+class TestSourcePackageRecipeBuildViewMixin:
867 """Test behaviour of SourcePackageRecipeBuildView."""
868
869 layer = LaunchpadFunctionalLayer
870
871 def setUp(self):
872 """Provide useful defaults."""
873- super(TestSourcePackageRecipeBuildView, self).setUp()
874+ super(TestSourcePackageRecipeBuildViewMixin, self).setUp()
875 self.user = self.factory.makePerson(
876 displayname='Owner', name='build-owner')
877
878 def makeBuild(self):
879- """Make a build suitabe for testing."""
880+ """Make a build suitable for testing."""
881 archive = self.factory.makeArchive(name='build',
882 owner=self.user)
883+ branch = self.makeBranch()
884 recipe = self.factory.makeSourcePackageRecipe(
885- owner=self.user, name=u'my-recipe')
886+ owner=self.user, name=u'my-recipe', branches=[branch])
887 distro_series = self.factory.makeDistroSeries(
888 name='squirrel', distribution=archive.distribution)
889 removeSecurityProxy(distro_series).nominatedarchindep = (
890@@ -1607,7 +1688,9 @@
891 It should be bq.date_started + estimated duration for jobs that have
892 started.
893 """
894- build = self.factory.makeSourcePackageRecipeBuild()
895+ branch = self.makeBranch()
896+ recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
897+ build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
898 view = SourcePackageRecipeBuildView(build, None)
899 self.assertIs(None, view.eta)
900 queue_entry = removeSecurityProxy(build.queueBuild())
901@@ -1750,12 +1833,24 @@
902 self.assertEqual(upload_log_url, link.url)
903
904
905-class TestSourcePackageRecipeDeleteView(TestCaseForRecipe):
906+class TestSourcePackageRecipeBuildViewBzr(
907+ TestSourcePackageRecipeBuildViewMixin, BzrMixin, BrowserTestCase):
908+ pass
909+
910+
911+class TestSourcePackageRecipeBuildViewGit(
912+ TestSourcePackageRecipeBuildViewMixin, GitMixin, BrowserTestCase):
913+ pass
914+
915+
916+class TestSourcePackageRecipeDeleteViewMixin:
917
918 layer = DatabaseFunctionalLayer
919
920 def test_delete_recipe(self):
921- recipe = self.factory.makeSourcePackageRecipe(owner=self.chef)
922+ branch = self.makeBranch()
923+ recipe = self.factory.makeSourcePackageRecipe(
924+ owner=self.chef, branches=[branch])
925
926 browser = self.getUserBrowser(
927 canonical_url(recipe), user=self.chef)
928@@ -1768,7 +1863,9 @@
929 browser.url)
930
931 def test_delete_recipe_no_permissions(self):
932- recipe = self.factory.makeSourcePackageRecipe(owner=self.chef)
933+ branch = self.makeBranch()
934+ recipe = self.factory.makeSourcePackageRecipe(
935+ owner=self.chef, branches=[branch])
936 nopriv_person = self.factory.makePerson()
937 recipe_url = canonical_url(recipe)
938
939@@ -1784,7 +1881,17 @@
940 self.getUserBrowser, recipe_url + '/+delete', user=nopriv_person)
941
942
943-class TestBrokenExistingRecipes(BrowserTestCase):
944+class TestSourcePackageRecipeDeleteViewBzr(
945+ TestSourcePackageRecipeDeleteViewMixin, BzrMixin, TestCaseForRecipe):
946+ pass
947+
948+
949+class TestSourcePackageRecipeDeleteViewGit(
950+ TestSourcePackageRecipeDeleteViewMixin, GitMixin, TestCaseForRecipe):
951+ pass
952+
953+
954+class TestBrokenExistingRecipesMixin:
955 """Existing recipes broken by builder updates need to be editable.
956
957 This happened with a 0.2 -> 0.3 release where the nest command was no
958@@ -1795,19 +1902,17 @@
959
960 layer = LaunchpadFunctionalLayer
961
962- RECIPE_FIRST_LINE = (
963- "# bzr-builder format 0.2 deb-version {debupstream}+{revno}")
964-
965 def makeBrokenRecipe(self):
966 """Make a valid recipe, then break it."""
967 product = self.factory.makeProduct()
968- b1 = self.factory.makeProductBranch(product=product)
969- b2 = self.factory.makeProductBranch(product=product)
970+ b1 = self.makeBranch(target=product)
971+ b2 = self.makeBranch(target=product)
972 recipe_text = dedent("""\
973 %s
974 %s
975 nest name %s foo
976- """ % (self.RECIPE_FIRST_LINE, b1.bzr_identity, b2.bzr_identity))
977+ """ % (self.RECIPE_FIRST_LINE, self.getBranchRecipeText(b1),
978+ self.getRepository(b2).identity))
979 recipe = self.factory.makeSourcePackageRecipe(recipe=recipe_text)
980 naked_data = removeSecurityProxy(recipe)._recipe_data
981 nest_instruction = list(naked_data.instructions)[0]
982@@ -1831,3 +1936,17 @@
983 recipe = self.makeBrokenRecipe()
984 main_text = self.getMainText(recipe, '+edit', user=recipe.owner)
985 self.assertRecipeInText(main_text)
986+
987+
988+class TestBrokenExistingRecipesBzr(
989+ TestBrokenExistingRecipesMixin, BzrMixin, BrowserTestCase):
990+
991+ RECIPE_FIRST_LINE = (
992+ "# bzr-builder format 0.2 deb-version {debupstream}+{revno}")
993+
994+
995+class TestBrokenExistingRecipesGit(
996+ TestBrokenExistingRecipesMixin, GitMixin, BrowserTestCase):
997+
998+ RECIPE_FIRST_LINE = (
999+ "# git-build-recipe format 0.4 deb-version {debupstream}+{revtime}")
1000
1001=== modified file 'lib/lp/code/templates/sourcepackagerecipe-index.pt'
1002--- lib/lp/code/templates/sourcepackagerecipe-index.pt 2014-05-14 09:23:56 +0000
1003+++ lib/lp/code/templates/sourcepackagerecipe-index.pt 2016-01-14 20:31:44 +0000
1004@@ -108,9 +108,9 @@
1005 <dt>Owner:</dt>
1006 <dd tal:content="structure view/person_picker"/>
1007 </dl>
1008- <dl id="base-branch">
1009- <dt>Base branch:</dt>
1010- <dd tal:content="structure context/base_branch/fmt:link" />
1011+ <dl id="base-source">
1012+ <dt>Base source:</dt>
1013+ <dd tal:content="structure context/base/fmt:link" />
1014 </dl>
1015 <dl id="debian-version">
1016 <dt>Debian version:</dt>
1017@@ -151,8 +151,8 @@
1018 Y.on('lp:context:deb_version_template:changed', function(e) {
1019 Y.lp.deprecated.ui.update_field('#debian-version dd', e.new_value);
1020 });
1021- Y.on('lp:context:base_branch_link:changed', function(e) {
1022- Y.lp.deprecated.ui.update_field('#base-branch dd', e.new_value_html);
1023+ Y.on('lp:context:base_source_link:changed', function(e) {
1024+ Y.lp.deprecated.ui.update_field('#base-source dd', e.new_value_html);
1025 });
1026 Y.on('load', function() {
1027 Y.lp.code.requestbuild_overlay.hookUpDailyBuildsSchedule();