Merge lp:~abentley/launchpad/permit-commands into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 11517
Proposed branch: lp:~abentley/launchpad/permit-commands
Merge into: lp:launchpad
Prerequisite: lp:~abentley/launchpad/recipe-interfaces
Diff against target: 494 lines (+68/-102)
9 files modified
lib/lp/code/browser/sourcepackagerecipe.py (+6/-8)
lib/lp/code/errors.py (+0/-9)
lib/lp/code/interfaces/sourcepackagerecipe.py (+2/-2)
lib/lp/code/model/sourcepackagerecipe.py (+7/-10)
lib/lp/code/model/sourcepackagerecipedata.py (+10/-8)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+36/-59)
lib/lp/registry/model/person.py (+1/-3)
lib/lp/testing/factory.py (+5/-2)
utilities/sourcedeps.conf (+1/-1)
To merge this branch: bzr merge lp:~abentley/launchpad/permit-commands
Reviewer Review Type Date Requested Status
Paul Hummer (community) Approve
Review via email: mp+34476@code.launchpad.net

Commit message

Update forbidden command handling to use bzr-builder facilities.

Description of the change

= Summary =
Update launchpad to use bzr-builder's facilities for using only safe
instructions.

== Proposed fix ==
Provide permitted_instructions to RecipeParser.parse

== Pre-implementation notes ==
None

== Implementation details ==
Several things are needed for this change. Crucially, there should be a single
codepath that is used to parse recipe text, so that no one can forget to supply
permitted_instructions. SourcePackageRecipeData.getParsedRecipe provides this.

From this, it made sense to change the constructor of SourcePackageRecipe to
take unparsed recipe text as instead of a parsed "builder recipe" as an
argument.

This required changing many tests.

Additionally, the check for forbidden instructions now happens at parse time,
instead of at as a later stage.

Additionally, tests were changed to use
LaunchpadObjectFactory.makeSourcePackageRecipe where possible. And for many
tests, it was simpler to use LaunchpadObjectFactory.makeRecipeText instead of
makeRecipe.

== Tests ==
bin/tests test_sourcepackagerecipe

== Demo and Q/A ==
None

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/configure.zcml
  lib/lp/code/model/sourcepackagerecipedata.py
  lib/lp/code/errors.py
  lib/lp/code/model/tests/test_sourcepackagerecipe.py
  lib/lp/testing/factory.py
  lib/lp/code/browser/sourcepackagerecipe.py
  utilities/sourcedeps.conf
  lib/lp/code/model/sourcepackagerecipe.py
  lib/lp/code/interfaces/sourcepackagerecipe.py
  lib/lp/registry/model/person.py

./lib/lp/code/model/sourcepackagerecipedata.py
     174: E202 whitespace before ')'
./lib/lp/code/errors.py
     142: E231 missing whitespace after ','
     172: E231 missing whitespace after ','
     180: E231 missing whitespace after ','
./lib/lp/code/model/tests/test_sourcepackagerecipe.py
     243: E501 line too long (80 characters)
     256: E231 missing whitespace after ','
     280: E231 missing whitespace after ','
     288: E231 missing whitespace after ','
     295: E231 missing whitespace after ','
     303: E231 missing whitespace after ','
     341: E231 missing whitespace after ','
     401: E231 missing whitespace after ','
     243: Line exceeds 78 characters.
./lib/lp/code/browser/sourcepackagerecipe.py
     163: E231 missing whitespace after ','
     271: E202 whitespace before ']'
     381: E231 missing whitespace after ','
./lib/lp/code/interfaces/sourcepackagerecipe.py
      77: E231 missing whitespace after ','
     121: E202 whitespace before ')'
     121: E231 missing whitespace after ','
     150: E302 expected 2 blank lines, found 1
./lib/lp/registry/model/person.py
    1617: E301 expected 1 blank line, found 0
    1624: E301 expected 1 blank line, found 0

To post a comment you must log in.
Revision history for this message
Paul Hummer (rockstar) wrote :

Thanks for simplifying the parsed recipe stuff.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
2--- lib/lp/code/browser/sourcepackagerecipe.py 2010-08-23 02:07:45 +0000
3+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-09-09 13:41:57 +0000
4@@ -16,6 +16,7 @@
5
6
7 from bzrlib.plugins.builder.recipe import (
8+ ForbiddenInstructionError,
9 RecipeParseError,
10 RecipeParser,
11 )
12@@ -57,7 +58,6 @@
13 from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget
14 from lp.code.errors import (
15 BuildAlreadyPending,
16- ForbiddenInstruction,
17 NoSuchBranch,
18 PrivateBranchRecipe,
19 )
20@@ -326,16 +326,14 @@
21
22 @action('Create Recipe', name='create')
23 def request_action(self, action, data):
24- parser = RecipeParser(data['recipe_text'])
25- recipe = parser.parse()
26 try:
27 source_package_recipe = getUtility(
28 ISourcePackageRecipeSource).new(
29- self.user, data['owner'], data['name'], recipe,
30- data['description'], data['distros'],
31+ self.user, data['owner'], data['name'],
32+ data['recipe_text'], data['description'], data['distros'],
33 data['daily_build_archive'], data['build_daily'])
34 Store.of(source_package_recipe).flush()
35- except ForbiddenInstruction:
36+ except ForbiddenInstructionError:
37 # XXX: bug=592513 We shouldn't be hardcoding "run" here.
38 self.setFieldError(
39 'recipe_text',
40@@ -397,9 +395,9 @@
41 recipe = parser.parse()
42 if self.context.builder_recipe != recipe:
43 try:
44- self.context.builder_recipe = recipe
45+ self.context.setRecipeText(recipe_text)
46 changed = True
47- except ForbiddenInstruction:
48+ except ForbiddenInstructionError:
49 # XXX: bug=592513 We shouldn't be hardcoding "run" here.
50 self.setFieldError(
51 'recipe_text',
52
53=== modified file 'lib/lp/code/errors.py'
54--- lib/lp/code/errors.py 2010-08-03 03:43:33 +0000
55+++ lib/lp/code/errors.py 2010-09-09 13:41:57 +0000
56@@ -25,7 +25,6 @@
57 'CodeImportAlreadyRunning',
58 'CodeImportNotInReviewedState',
59 'ClaimReviewFailed',
60- 'ForbiddenInstruction',
61 'InvalidBranchMergeProposal',
62 'InvalidNamespace',
63 'NoLinkedBranch',
64@@ -242,14 +241,6 @@
65 webservice_error(400)
66
67
68-class ForbiddenInstruction(Exception):
69- """A forbidden instruction was found in the recipe."""
70-
71- def __init__(self, instruction_name):
72- super(ForbiddenInstruction, self).__init__()
73- self.instruction_name = instruction_name
74-
75-
76 class TooNewRecipeFormat(Exception):
77 """The format of the recipe supplied was too new."""
78
79
80=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
81--- lib/lp/code/interfaces/sourcepackagerecipe.py 2010-09-09 13:41:38 +0000
82+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2010-09-09 13:41:57 +0000
83@@ -93,8 +93,6 @@
84 required=True, readonly=True,
85 vocabulary='ValidPersonOrTeam'))
86
87- is_stale = Bool(title=_('Recipe is stale.'))
88-
89 recipe_text = exported(Text())
90
91 def isOverQuota(requester, distroseries):
92@@ -183,6 +181,8 @@
93
94 date_last_modified = Datetime(required=True, readonly=True)
95
96+ is_stale = Bool(title=_('Recipe is stale.'))
97+
98
99 class ISourcePackageRecipe(ISourcePackageRecipeData,
100 ISourcePackageRecipeEdit, ISourcePackageRecipeEditableAttributes,
101
102=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
103--- lib/lp/code/model/sourcepackagerecipe.py 2010-09-01 03:25:36 +0000
104+++ lib/lp/code/model/sourcepackagerecipe.py 2010-09-09 13:41:57 +0000
105@@ -11,7 +11,6 @@
106 'SourcePackageRecipe',
107 ]
108
109-from bzrlib.plugins.builder.recipe import RecipeParser
110 from lazr.delegates import delegates
111 from storm.locals import (
112 Bool,
113@@ -139,33 +138,30 @@
114 SourcePackageRecipeData,
115 SourcePackageRecipeData.sourcepackage_recipe == self).one()
116
117- def _get_builder_recipe(self):
118+ @property
119+ def builder_recipe(self):
120 """Accesses of the recipe go to the SourcePackageRecipeData."""
121 return self._recipe_data.getRecipe()
122
123- def _set_builder_recipe(self, value):
124- """Setting of the recipe goes to the SourcePackageRecipeData."""
125- self._recipe_data.setRecipe(value)
126-
127- builder_recipe = property(_get_builder_recipe, _set_builder_recipe)
128-
129 @property
130 def base_branch(self):
131 return self._recipe_data.base_branch
132
133 def setRecipeText(self, recipe_text):
134- self.builder_recipe = RecipeParser(recipe_text).parse()
135+ parsed = SourcePackageRecipeData.getParsedRecipe(recipe_text)
136+ self._recipe_data.setRecipe(parsed)
137
138 @property
139 def recipe_text(self):
140 return str(self.builder_recipe)
141
142 @staticmethod
143- def new(registrant, owner, name, builder_recipe, description,
144+ def new(registrant, owner, name, recipe, description,
145 distroseries=None, daily_build_archive=None, build_daily=False):
146 """See `ISourcePackageRecipeSource.new`."""
147 store = IMasterStore(SourcePackageRecipe)
148 sprecipe = SourcePackageRecipe()
149+ builder_recipe = SourcePackageRecipeData.getParsedRecipe(recipe)
150 SourcePackageRecipeData(builder_recipe, sprecipe)
151 sprecipe.registrant = registrant
152 sprecipe.owner = owner
153@@ -201,6 +197,7 @@
154 store = Store.of(self)
155 self.distroseries.clear()
156 self._recipe_data.instructions.find().remove()
157+
158 def destroyBuilds(pending):
159 builds = self.getBuilds(pending=pending)
160 for build in builds:
161
162=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
163--- lib/lp/code/model/sourcepackagerecipedata.py 2010-08-20 20:31:18 +0000
164+++ lib/lp/code/model/sourcepackagerecipedata.py 2010-09-09 13:41:57 +0000
165@@ -18,6 +18,8 @@
166 MergeInstruction,
167 NestInstruction,
168 RecipeBranch,
169+ RecipeParser,
170+ SAFE_INSTRUCTIONS,
171 )
172 from lazr.enum import (
173 DBEnumeratedType,
174@@ -39,7 +41,6 @@
175 from canonical.database.enumcol import EnumCol
176 from canonical.launchpad.interfaces.lpstorm import IStore
177 from lp.code.errors import (
178- ForbiddenInstruction,
179 NoSuchBranch,
180 PrivateBranchRecipe,
181 TooNewRecipeFormat,
182@@ -151,6 +152,11 @@
183 sourcepackage_recipe_build_id, 'SourcePackageRecipeBuild.id')
184
185 @staticmethod
186+ def getParsedRecipe(recipe_text):
187+ parser = RecipeParser(recipe_text)
188+ return parser.parse(permitted_instructions=SAFE_INSTRUCTIONS)
189+
190+ @staticmethod
191 def findRecipes(branch):
192 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
193 store = Store.of(branch)
194@@ -179,10 +185,9 @@
195 with.
196 :return: an instance of SourcePackageRecipeData.
197 """
198- from bzrlib.plugins.builder.recipe import RecipeParser
199- parser = RecipeParser(text)
200- return cls(parser.parse(),
201- sourcepackage_recipe_build=sourcepackage_recipe_build)
202+ parsed = cls.getParsedRecipe(text)
203+ return cls(
204+ parsed, sourcepackage_recipe_build=sourcepackage_recipe_build)
205
206 def getRecipe(self):
207 """The BaseRecipeBranch version of the recipe."""
208@@ -213,9 +218,6 @@
209 """
210 r = {}
211 for instruction in recipe_branch.child_branches:
212- if not (isinstance(instruction, MergeInstruction) or
213- isinstance(instruction, NestInstruction)):
214- raise ForbiddenInstruction(str(instruction))
215 db_branch = getUtility(IBranchLookup).getByUrl(
216 instruction.recipe_branch.url)
217 if db_branch is None:
218
219=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
220--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-09-09 13:41:38 +0000
221+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-09-09 13:41:57 +0000
222@@ -14,7 +14,9 @@
223 import textwrap
224 import unittest
225
226-from bzrlib.plugins.builder.recipe import RecipeParser
227+from bzrlib.plugins.builder.recipe import (
228+ ForbiddenInstructionError,
229+)
230 from pytz import UTC
231 from storm.locals import Store
232 import transaction
233@@ -33,7 +35,6 @@
234 from lp.buildmaster.model.buildqueue import BuildQueue
235 from lp.code.errors import (
236 BuildAlreadyPending,
237- ForbiddenInstruction,
238 PrivateBranchRecipe,
239 TooManyBuilds,
240 TooNewRecipeFormat,
241@@ -84,18 +85,6 @@
242 recipe = self.factory.makeSourcePackageRecipe()
243 verifyObject(ISourcePackageRecipe, recipe)
244
245- def makeSourcePackageRecipeFromBuilderRecipe(self, builder_recipe):
246- """Make a SourcePackageRecipe from a recipe with arbitrary other data.
247- """
248- registrant = self.factory.makePerson()
249- owner = self.factory.makeTeam(owner=registrant)
250- distroseries = self.factory.makeDistroSeries()
251- name = self.factory.getUniqueString(u'recipe-name')
252- description = self.factory.getUniqueString(u'recipe-description')
253- return getUtility(ISourcePackageRecipeSource).new(
254- registrant=registrant, owner=owner, distroseries=[distroseries],
255- name=name, description=description, builder_recipe=builder_recipe)
256-
257 def makeRecipeComponents(self, branches=()):
258 """Return a dict of values that can be used to make a recipe.
259
260@@ -110,7 +99,7 @@
261 distroseries = [self.factory.makeDistroSeries()],
262 name = self.factory.getUniqueString(u'recipe-name'),
263 description = self.factory.getUniqueString(u'recipe-description'),
264- builder_recipe = self.factory.makeRecipe(*branches))
265+ recipe = self.factory.makeRecipeText(*branches))
266
267 def test_creation(self):
268 # The metadata supplied when a SourcePackageRecipe is created is
269@@ -174,8 +163,7 @@
270
271 def test_recipe_implements_interface(self):
272 # SourcePackageRecipe objects implement ISourcePackageRecipe.
273- recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
274- self.factory.makeRecipe())
275+ recipe = self.factory.makeSourcePackageRecipe()
276 transaction.commit()
277 with person_logged_in(recipe.owner):
278 self.assertProvides(recipe, ISourcePackageRecipe)
279@@ -183,9 +171,7 @@
280 def test_base_branch(self):
281 # When a recipe is created, we can access its base branch.
282 branch = self.factory.makeAnyBranch()
283- builder_recipe = self.factory.makeRecipe(branch)
284- sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
285- builder_recipe)
286+ sp_recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
287 transaction.commit()
288 self.assertEquals(branch, sp_recipe.base_branch)
289
290@@ -193,9 +179,8 @@
291 # When a recipe is created, we can query it for links to the branch
292 # it references.
293 branch = self.factory.makeAnyBranch()
294- builder_recipe = self.factory.makeRecipe(branch)
295- sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
296- builder_recipe)
297+ sp_recipe = self.factory.makeSourcePackageRecipe(
298+ branches=[branch])
299 transaction.commit()
300 self.assertEquals([branch], list(sp_recipe.getReferencedBranches()))
301
302@@ -204,9 +189,8 @@
303 # returns all of them.
304 branch1 = self.factory.makeAnyBranch()
305 branch2 = self.factory.makeAnyBranch()
306- builder_recipe = self.factory.makeRecipe(branch1, branch2)
307- sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
308- builder_recipe)
309+ sp_recipe = self.factory.makeSourcePackageRecipe(
310+ branches=[branch1, branch2])
311 transaction.commit()
312 self.assertEquals(
313 sorted([branch1, branch2]),
314@@ -215,27 +199,23 @@
315 def test_random_user_cant_edit(self):
316 # An arbitrary user can't set attributes.
317 branch1 = self.factory.makeAnyBranch()
318- builder_recipe1 = self.factory.makeRecipe(branch1)
319- sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
320- builder_recipe1)
321- branch2 = self.factory.makeAnyBranch()
322- builder_recipe2 = self.factory.makeRecipe(branch2)
323+ recipe_1 = self.factory.makeRecipeText(branch1)
324+ sp_recipe = self.factory.makeSourcePackageRecipe(
325+ recipe=recipe_1)
326 login_person(self.factory.makePerson())
327 self.assertRaises(
328- Unauthorized, setattr, sp_recipe, 'builder_recipe',
329- builder_recipe2)
330+ Unauthorized, getattr, sp_recipe, 'setRecipeText')
331
332 def test_set_recipe_text_resets_branch_references(self):
333 # When the recipe_text is replaced, getReferencedBranches returns
334 # (only) the branches referenced by the new recipe.
335 branch1 = self.factory.makeAnyBranch()
336- builder_recipe1 = self.factory.makeRecipe(branch1)
337- sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
338- builder_recipe1)
339+ sp_recipe = self.factory.makeSourcePackageRecipe(
340+ branches=[branch1])
341 branch2 = self.factory.makeAnyBranch()
342- builder_recipe2 = self.factory.makeRecipe(branch2)
343- login_person(sp_recipe.owner.teamowner)
344- sp_recipe.builder_recipe = builder_recipe2
345+ new_recipe = self.factory.makeRecipeText(branch2)
346+ with person_logged_in(sp_recipe.owner):
347+ sp_recipe.setRecipeText(new_recipe)
348 self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))
349
350 def test_rejects_run_command(self):
351@@ -244,36 +224,32 @@
352 %(base)s
353 run touch test
354 ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
355- parser = RecipeParser(textwrap.dedent(recipe_text))
356- builder_recipe = parser.parse()
357+ recipe_text = textwrap.dedent(recipe_text)
358 self.assertRaises(
359- ForbiddenInstruction,
360- self.makeSourcePackageRecipeFromBuilderRecipe, builder_recipe)
361+ ForbiddenInstructionError, self.factory.makeSourcePackageRecipe,
362+ recipe=recipe_text)
363
364 def test_run_rejected_without_mangling_recipe(self):
365- branch1 = self.factory.makeAnyBranch()
366- builder_recipe1 = self.factory.makeRecipe(branch1)
367- sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
368- builder_recipe1)
369+ sp_recipe = self.factory.makeSourcePackageRecipe()
370+ old_branches = list(sp_recipe.getReferencedBranches())
371 recipe_text = '''\
372 # bzr-builder format 0.2 deb-version 0.1-{revno}
373 %(base)s
374 run touch test
375 ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
376- parser = RecipeParser(textwrap.dedent(recipe_text))
377- builder_recipe2 = parser.parse()
378- login_person(sp_recipe.owner.teamowner)
379- self.assertRaises(
380- ForbiddenInstruction, setattr, sp_recipe, 'builder_recipe',
381- builder_recipe2)
382- self.assertEquals([branch1], list(sp_recipe.getReferencedBranches()))
383+ recipe_text = textwrap.dedent(recipe_text)
384+ with person_logged_in(sp_recipe.owner):
385+ self.assertRaises(
386+ ForbiddenInstructionError, sp_recipe.setRecipeText, recipe_text)
387+ self.assertEquals(
388+ old_branches, list(sp_recipe.getReferencedBranches()))
389
390 def test_reject_newer_formats(self):
391 builder_recipe = self.factory.makeRecipe()
392 builder_recipe.format = 0.3
393 self.assertRaises(
394 TooNewRecipeFormat,
395- self.makeSourcePackageRecipeFromBuilderRecipe, builder_recipe)
396+ self.factory.makeSourcePackageRecipe, recipe=str(builder_recipe))
397
398 def test_requestBuild(self):
399 recipe = self.factory.makeSourcePackageRecipe()
400@@ -503,6 +479,7 @@
401 SourcePackageRecipe.findStaleDailyBuilds())
402
403 def test_getMedianBuildDuration(self):
404+
405 def set_duration(build, minutes):
406 duration = timedelta(minutes=minutes)
407 build = removeSecurityProxy(build)
408@@ -542,7 +519,7 @@
409 }
410
411 def get_recipe(self, recipe_text):
412- builder_recipe = RecipeParser(textwrap.dedent(recipe_text)).parse()
413+ recipe_text = textwrap.dedent(recipe_text)
414 registrant = self.factory.makePerson()
415 owner = self.factory.makeTeam(owner=registrant)
416 distroseries = self.factory.makeDistroSeries()
417@@ -550,7 +527,7 @@
418 description = self.factory.getUniqueString(u'recipe-description')
419 recipe = getUtility(ISourcePackageRecipeSource).new(
420 registrant=registrant, owner=owner, distroseries=[distroseries],
421- name=name, description=description, builder_recipe=builder_recipe)
422+ name=name, description=description, recipe=recipe_text)
423 transaction.commit()
424 return recipe.builder_recipe
425
426@@ -724,8 +701,8 @@
427 return MINIMAL_RECIPE_TEXT % branch.bzr_identity
428
429 def makeRecipe(self, user=None, owner=None, recipe_text=None):
430- # rockstar 21 Jul 2010 - This function does more commits than I'd like,
431- # but it's the result of the fact that the webservice runs in a
432+ # rockstar 21 Jul 2010 - This function does more commits than I'd
433+ # like, but it's the result of the fact that the webservice runs in a
434 # separate thread so doesn't get the database updates without those
435 # commits.
436 if user is None:
437
438=== modified file 'lib/lp/registry/model/person.py'
439--- lib/lp/registry/model/person.py 2010-09-03 16:43:11 +0000
440+++ lib/lp/registry/model/person.py 2010-09-09 13:41:57 +0000
441@@ -36,7 +36,6 @@
442 import subprocess
443 import weakref
444
445-from bzrlib.plugins.builder.recipe import RecipeParser
446 import pytz
447 from sqlobject import (
448 BoolCol,
449@@ -2634,9 +2633,8 @@
450 registrant, daily_build_archive=None, build_daily=False):
451 """See `IPerson`."""
452 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
453- builder_recipe = RecipeParser(recipe_text).parse()
454 recipe = SourcePackageRecipe.new(
455- registrant, self, name, builder_recipe, description, distroseries,
456+ registrant, self, name, recipe_text, description, distroseries,
457 daily_build_archive, build_daily)
458 Store.of(recipe).flush()
459 return recipe
460
461=== modified file 'lib/lp/testing/factory.py'
462--- lib/lp/testing/factory.py 2010-09-03 16:43:11 +0000
463+++ lib/lp/testing/factory.py 2010-09-09 13:41:57 +0000
464@@ -2035,7 +2035,7 @@
465 distroseries=None, name=None,
466 description=None, branches=(),
467 build_daily=False, daily_build_archive=None,
468- is_stale=None):
469+ is_stale=None, recipe=None):
470 """Make a `SourcePackageRecipe`."""
471 if registrant is None:
472 registrant = self.makePerson()
473@@ -2051,7 +2051,10 @@
474 if daily_build_archive is None:
475 daily_build_archive = self.makeArchive(
476 distribution=distroseries.distribution, owner=owner)
477- recipe = self.makeRecipe(*branches)
478+ if recipe is None:
479+ recipe = self.makeRecipeText(*branches)
480+ else:
481+ assert branches == ()
482 source_package_recipe = getUtility(ISourcePackageRecipeSource).new(
483 registrant, owner, name, recipe, description, [distroseries],
484 daily_build_archive, build_daily)
485
486=== modified file 'utilities/sourcedeps.conf'
487--- utilities/sourcedeps.conf 2010-09-03 03:12:39 +0000
488+++ utilities/sourcedeps.conf 2010-09-09 13:41:57 +0000
489@@ -1,4 +1,4 @@
490-bzr-builder lp:~launchpad-pqm/bzr-builder/trunk;revno=65
491+bzr-builder lp:~launchpad-pqm/bzr-builder/trunk;revno=66
492 bzr-git lp:~launchpad-pqm/bzr-git/devel;revno=258
493 bzr-hg lp:~launchpad-pqm/bzr-hg/devel;revno=283
494 bzr-loom lp:~launchpad-pqm/bzr-loom/trunk;revno=48