Merge lp:~cjwatson/launchpad/git-recipe-find into lp:launchpad

Proposed by Colin Watson
Status: Superseded
Proposed branch: lp:~cjwatson/launchpad/git-recipe-find
Merge into: lp:launchpad
Diff against target: 2177 lines (+693/-271)
17 files modified
lib/lp/code/browser/sourcepackagerecipe.py (+17/-16)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+8/-8)
lib/lp/code/configure.zcml (+10/-1)
lib/lp/code/errors.py (+14/-3)
lib/lp/code/interfaces/gitref.py (+3/-2)
lib/lp/code/interfaces/gitrepository.py (+3/-2)
lib/lp/code/interfaces/sourcepackagerecipe.py (+43/-4)
lib/lp/code/model/gitref.py (+13/-1)
lib/lp/code/model/gitrepository.py (+25/-1)
lib/lp/code/model/sourcepackagerecipe.py (+26/-4)
lib/lp/code/model/sourcepackagerecipebuild.py (+10/-6)
lib/lp/code/model/sourcepackagerecipedata.py (+180/-48)
lib/lp/code/model/tests/test_hasrecipes.py (+28/-1)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+274/-163)
lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+4/-4)
lib/lp/registry/model/product.py (+15/-3)
lib/lp/testing/factory.py (+20/-4)
To merge this branch: bzr merge lp:~cjwatson/launchpad/git-recipe-find
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+282252@code.launchpad.net

This proposal has been superseded by a proposal from 2016-01-12.

Commit message

Add IHasRecipes implementations and other methods for finding Git recipes.

Description of the change

Add IHasRecipes implementations and other methods for finding Git recipes.

The changes to Product.recipes aren't specifically tested here, mainly because the existing tests for that rely on having browser code in place. I have test changes for that which I'll propose in a later branch.

To post a comment you must log in.

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 2015-07-08 16:05:11 +0000
3+++ lib/lp/code/browser/sourcepackagerecipe.py 2016-01-12 04:06:05 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2010-2013 Canonical Ltd. This software is licensed under the
6+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """SourcePackageRecipe views."""
10@@ -20,7 +20,6 @@
11 from bzrlib.plugins.builder.recipe import (
12 ForbiddenInstructionError,
13 RecipeParseError,
14- RecipeParser,
15 )
16 from lazr.lifecycle.event import ObjectModifiedEvent
17 from lazr.lifecycle.snapshot import Snapshot
18@@ -92,9 +91,10 @@
19 )
20 from lp.code.interfaces.branchtarget import IBranchTarget
21 from lp.code.interfaces.sourcepackagerecipe import (
22+ IRecipeBranchSource,
23 ISourcePackageRecipe,
24 ISourcePackageRecipeSource,
25- MINIMAL_RECIPE_TEXT,
26+ MINIMAL_RECIPE_TEXT_BZR,
27 )
28 from lp.code.model.branchtarget import PersonBranchTarget
29 from lp.code.vocabularies.sourcepackagerecipe import BuildableDistroSeries
30@@ -611,10 +611,11 @@
31 'distroseries',
32 'You must specify at least one series for daily builds.')
33 try:
34- parser = RecipeParser(data['recipe_text'])
35- parser.parse()
36- except RecipeParseError as error:
37- self.setFieldError('recipe_text', str(error))
38+ self.error_handler(
39+ getUtility(IRecipeBranchSource).getParsedRecipe,
40+ data['recipe_text'])
41+ except ErrorHandled:
42+ pass
43
44 def error_handler(self, callable, *args, **kwargs):
45 try:
46@@ -631,7 +632,7 @@
47 except NoSuchBranch as e:
48 self.setFieldError(
49 'recipe_text', '%s is not a branch on Launchpad.' % e.name)
50- except PrivateBranchRecipe as e:
51+ except (RecipeParseError, PrivateBranchRecipe) as e:
52 self.setFieldError('recipe_text', str(e))
53 raise ErrorHandled()
54
55@@ -760,7 +761,7 @@
56 SeriesStatus.CURRENT, SeriesStatus.DEVELOPMENT)]
57 return {
58 'name': self._find_unused_name(self.user),
59- 'recipe_text': MINIMAL_RECIPE_TEXT % self.context.bzr_identity,
60+ 'recipe_text': MINIMAL_RECIPE_TEXT_BZR % self.context.bzr_identity,
61 'owner': self.user,
62 'distroseries': series,
63 'build_daily': True,
64@@ -872,14 +873,14 @@
65 self.context, providing=providedBy(self.context))
66
67 recipe_text = data.pop('recipe_text')
68- parser = RecipeParser(recipe_text)
69- recipe = parser.parse()
70- if self.context.builder_recipe != recipe:
71- try:
72+ try:
73+ recipe = self.error_handler(
74+ getUtility(IRecipeBranchSource).getParsedRecipe, recipe_text)
75+ if self.context.builder_recipe != recipe:
76 self.error_handler(self.context.setRecipeText, recipe_text)
77 changed = True
78- except ErrorHandled:
79- return
80+ except ErrorHandled:
81+ return
82
83 distros = data.pop('distroseries')
84 if distros != self.context.distroseries:
85@@ -927,7 +928,7 @@
86 label = title
87
88 class schema(Interface):
89- """Schema for deleting a branch."""
90+ """Schema for deleting a recipe."""
91
92 @property
93 def cancel_url(self):
94
95=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
96--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2015-09-28 17:38:45 +0000
97+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2016-01-12 04:06:05 +0000
98@@ -34,7 +34,7 @@
99 from lp.code.browser.sourcepackagerecipebuild import (
100 SourcePackageRecipeBuildView,
101 )
102-from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT
103+from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT_BZR
104 from lp.code.tests.helpers import recipe_parser_newest_version
105 from lp.registry.interfaces.person import TeamMembershipPolicy
106 from lp.registry.interfaces.pocket import PackagePublishingPocket
107@@ -449,7 +449,7 @@
108 def test_create_recipe_bad_base_branch(self):
109 # If a user tries to create source package recipe with a bad base
110 # branch location, they should get an error.
111- browser = self.createRecipe(MINIMAL_RECIPE_TEXT % 'foo')
112+ browser = self.createRecipe(MINIMAL_RECIPE_TEXT_BZR % 'foo')
113 self.assertEqual(
114 get_feedback_messages(browser.contents)[1],
115 'foo is not a branch on Launchpad.')
116@@ -461,7 +461,7 @@
117 name='ratatouille', displayname='Ratatouille')
118 branch = self.factory.makeBranch(
119 owner=self.chef, product=product, name='veggies')
120- recipe = MINIMAL_RECIPE_TEXT % branch.bzr_identity
121+ recipe = MINIMAL_RECIPE_TEXT_BZR % branch.bzr_identity
122 recipe += 'nest packaging foo debian'
123 browser = self.createRecipe(recipe, branch)
124 self.assertEqual(
125@@ -518,7 +518,7 @@
126 owner=self.user, information_type=InformationType.USERDATA)
127 with person_logged_in(self.user):
128 bzr_identity = branch.bzr_identity
129- recipe_text = MINIMAL_RECIPE_TEXT % bzr_identity
130+ recipe_text = MINIMAL_RECIPE_TEXT_BZR % bzr_identity
131 browser = self.createRecipe(recipe_text)
132 self.assertEqual(
133 get_feedback_messages(browser.contents)[1],
134@@ -761,7 +761,7 @@
135 browser.getControl(name='field.name').value = 'fings'
136 browser.getControl('Description').value = 'This is stuff'
137 browser.getControl('Recipe text').value = (
138- MINIMAL_RECIPE_TEXT % meat_path)
139+ MINIMAL_RECIPE_TEXT_BZR % meat_path)
140 browser.getControl('Secret Squirrel').click()
141 browser.getControl('Mumbly Midget').click()
142 browser.getControl('PPA 2').click()
143@@ -828,7 +828,7 @@
144 browser.getControl(name='field.name').value = 'fings'
145 browser.getControl('Description').value = 'This is stuff'
146 browser.getControl('Recipe text').value = (
147- MINIMAL_RECIPE_TEXT % meat_path)
148+ MINIMAL_RECIPE_TEXT_BZR % meat_path)
149 browser.getControl('Secret Squirrel').click()
150 browser.getControl('Mumbly Midget').click()
151 browser.getControl('Update Recipe').click()
152@@ -926,7 +926,7 @@
153 browser.getControl(name='field.name').value = 'fings'
154 browser.getControl('Description').value = 'This is stuff'
155 browser.getControl('Recipe text').value = (
156- MINIMAL_RECIPE_TEXT % meat_path)
157+ MINIMAL_RECIPE_TEXT_BZR % meat_path)
158 browser.getControl('Secret Squirrel').click()
159 browser.getControl('Mumbly Midget').click()
160 browser.getControl('Update Recipe').click()
161@@ -943,7 +943,7 @@
162 owner=self.user, information_type=InformationType.USERDATA)
163 with person_logged_in(self.user):
164 bzr_identity = branch.bzr_identity
165- recipe_text = MINIMAL_RECIPE_TEXT % bzr_identity
166+ recipe_text = MINIMAL_RECIPE_TEXT_BZR % bzr_identity
167 browser = self.getViewBrowser(recipe, '+edit')
168 browser.getControl('Recipe text').value = recipe_text
169 browser.getControl('Update Recipe').click()
170
171=== modified file 'lib/lp/code/configure.zcml'
172--- lib/lp/code/configure.zcml 2015-10-09 16:56:45 +0000
173+++ lib/lp/code/configure.zcml 2016-01-12 04:06:05 +0000
174@@ -1,4 +1,4 @@
175-<!-- Copyright 2009-2015 Canonical Ltd. This software is licensed under the
176+<!-- Copyright 2009-2016 Canonical Ltd. This software is licensed under the
177 GNU Affero General Public License version 3 (see the file LICENSE).
178 -->
179
180@@ -1053,6 +1053,15 @@
181 <require permission="launchpad.View"
182 interface="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipeData"/>
183 </class>
184+ <utility
185+ component="lp.code.model.sourcepackagerecipedata.SourcePackageRecipeData"
186+ provides="lp.code.interfaces.sourcepackagerecipe.IRecipeBranchSource">
187+ </utility>
188+ <securedutility
189+ component="lp.code.model.sourcepackagerecipedata.SourcePackageRecipeData"
190+ provides="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipeDataSource">
191+ <allow interface="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipeDataSource"/>
192+ </securedutility>
193 <!-- SourcePackageRecipe -->
194 <class
195 class="lp.code.model.sourcepackagerecipe.SourcePackageRecipe">
196
197=== modified file 'lib/lp/code/errors.py'
198--- lib/lp/code/errors.py 2015-06-13 01:45:19 +0000
199+++ lib/lp/code/errors.py 2016-01-12 04:06:05 +0000
200@@ -1,4 +1,4 @@
201-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
202+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
203 # GNU Affero General Public License version 3 (see the file LICENSE).
204
205 """Errors used in the lp/code modules."""
206@@ -46,6 +46,7 @@
207 'NoSuchGitReference',
208 'NoSuchGitRepository',
209 'PrivateBranchRecipe',
210+ 'PrivateGitRepositoryRecipe',
211 'ReviewNotPending',
212 'StaleLastMirrored',
213 'TooNewRecipeFormat',
214@@ -298,12 +299,22 @@
215
216 def __init__(self, branch):
217 message = (
218- 'Recipe may not refer to private branch: %s' %
219- branch.bzr_identity)
220+ 'Recipe may not refer to private branch: %s' % branch.identity)
221 self.branch = branch
222 Exception.__init__(self, message)
223
224
225+@error_status(httplib.BAD_REQUEST)
226+class PrivateGitRepositoryRecipe(Exception):
227+
228+ def __init__(self, repository):
229+ message = (
230+ 'Recipe may not refer to private repository: %s' %
231+ repository.identity)
232+ self.repository = repository
233+ Exception.__init__(self, message)
234+
235+
236 class ReviewNotPending(Exception):
237 """The requested review is not in a pending state."""
238
239
240=== modified file 'lib/lp/code/interfaces/gitref.py'
241--- lib/lp/code/interfaces/gitref.py 2015-11-23 11:34:15 +0000
242+++ lib/lp/code/interfaces/gitref.py 2016-01-12 04:06:05 +0000
243@@ -1,4 +1,4 @@
244-# Copyright 2015 Canonical Ltd. This software is licensed under the
245+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
246 # GNU Affero General Public License version 3 (see the file LICENSE).
247
248 """Git reference ("ref") interfaces."""
249@@ -47,11 +47,12 @@
250 GitObjectType,
251 )
252 from lp.code.interfaces.hasbranches import IHasMergeProposals
253+from lp.code.interfaces.hasrecipes import IHasRecipes
254 from lp.registry.interfaces.person import IPerson
255 from lp.services.webapp.interfaces import ITableBatchNavigator
256
257
258-class IGitRef(IHasMergeProposals, IPrivacy, IInformationType):
259+class IGitRef(IHasMergeProposals, IHasRecipes, IPrivacy, IInformationType):
260 """A reference in a Git repository."""
261
262 # XXX cjwatson 2015-01-19 bug=760849: "beta" is a lie to get WADL
263
264=== modified file 'lib/lp/code/interfaces/gitrepository.py'
265--- lib/lp/code/interfaces/gitrepository.py 2015-11-02 15:31:39 +0000
266+++ lib/lp/code/interfaces/gitrepository.py 2016-01-12 04:06:05 +0000
267@@ -1,4 +1,4 @@
268-# Copyright 2015 Canonical Ltd. This software is licensed under the
269+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
270 # GNU Affero General Public License version 3 (see the file LICENSE).
271
272 """Git repository interfaces."""
273@@ -64,6 +64,7 @@
274 )
275 from lp.code.interfaces.defaultgit import ICanHasDefaultGitRepository
276 from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
277+from lp.code.interfaces.hasrecipes import IHasRecipes
278 from lp.registry.interfaces.distributionsourcepackage import (
279 IDistributionSourcePackage,
280 )
281@@ -118,7 +119,7 @@
282 return True
283
284
285-class IGitRepositoryView(Interface):
286+class IGitRepositoryView(IHasRecipes):
287 """IGitRepository attributes that require launchpad.View permission."""
288
289 id = Int(title=_("ID"), readonly=True, required=True)
290
291=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
292--- lib/lp/code/interfaces/sourcepackagerecipe.py 2015-04-30 01:45:30 +0000
293+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2016-01-12 04:06:05 +0000
294@@ -1,4 +1,4 @@
295-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
296+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
297 # GNU Affero General Public License version 3 (see the file LICENSE).
298
299 """Interface of the `SourcePackageRecipe` content type."""
300@@ -8,10 +8,13 @@
301
302
303 __all__ = [
304+ 'IRecipeBranchSource',
305 'ISourcePackageRecipe',
306 'ISourcePackageRecipeData',
307+ 'ISourcePackageRecipeDataSource',
308 'ISourcePackageRecipeSource',
309- 'MINIMAL_RECIPE_TEXT',
310+ 'MINIMAL_RECIPE_TEXT_BZR',
311+ 'MINIMAL_RECIPE_TEXT_GIT',
312 ]
313
314
315@@ -53,6 +56,7 @@
316 from lp import _
317 from lp.app.validators.name import name_validator
318 from lp.code.interfaces.branch import IBranch
319+from lp.code.interfaces.gitrepository import IGitRepository
320 from lp.registry.interfaces.distroseries import IDistroSeries
321 from lp.registry.interfaces.pocket import PackagePublishingPocket
322 from lp.registry.interfaces.role import IHasOwner
323@@ -64,19 +68,32 @@
324 from lp.soyuz.interfaces.archive import IArchive
325
326
327-MINIMAL_RECIPE_TEXT = dedent(u'''\
328+MINIMAL_RECIPE_TEXT_BZR = dedent(u'''\
329 # bzr-builder format 0.3 deb-version {debupstream}-0~{revno}
330 %s
331 ''')
332
333
334+MINIMAL_RECIPE_TEXT_GIT = dedent(u'''\
335+ # git-build-recipe format 0.4 deb-version {debupstream}-0~{revtime}
336+ %s %s
337+ ''')
338+
339+
340 class ISourcePackageRecipeData(Interface):
341 """A recipe as database data, not text."""
342
343 base_branch = exported(
344 Reference(
345 IBranch, title=_("The base branch used by this recipe."),
346- required=True, readonly=True))
347+ required=False, readonly=True))
348+ base_git_repository = exported(
349+ Reference(
350+ IGitRepository,
351+ title=_("The base Git repository used by this recipe."),
352+ required=False, readonly=True))
353+ base = Attribute(
354+ "The base branch/repository used by this recipe (VCS-agnostic).")
355
356 deb_version_template = exported(
357 TextLine(
358@@ -88,6 +105,28 @@
359 """An iterator of the branches referenced by this recipe."""
360
361
362+class IRecipeBranchSource(Interface):
363+
364+ def getParsedRecipe(recipe_text):
365+ """Parse recipe text into recipe data.
366+
367+ :param recipe_text: Recipe text as a string.
368+ :return: a `RecipeBranch` representing the recipe.
369+ """
370+
371+
372+class ISourcePackageRecipeDataSource(Interface):
373+
374+ def createManifestFromText(text, sourcepackage_recipe_build):
375+ """Create a manifest for the specified build.
376+
377+ :param text: The text of the recipe to create a manifest for.
378+ :param sourcepackage_recipe_build: The build to associate the manifest
379+ with.
380+ :return: an instance of `SourcePackageRecipeData`.
381+ """
382+
383+
384 class ISourcePackageRecipeView(Interface):
385 """IBranch attributes that require launchpad.View permission."""
386
387
388=== modified file 'lib/lp/code/model/gitref.py'
389--- lib/lp/code/model/gitref.py 2015-11-23 11:34:15 +0000
390+++ lib/lp/code/model/gitref.py 2016-01-12 04:06:05 +0000
391@@ -1,4 +1,4 @@
392-# Copyright 2015 Canonical Ltd. This software is licensed under the
393+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
394 # GNU Affero General Public License version 3 (see the file LICENSE).
395
396 __metaclass__ = type
397@@ -242,6 +242,18 @@
398 """See `IGitRef`."""
399 return self.repository.pending_writes
400
401+ @property
402+ def recipes(self):
403+ """See `IHasRecipes`."""
404+ from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
405+ from lp.code.model.sourcepackagerecipedata import (
406+ SourcePackageRecipeData,
407+ )
408+ recipes = SourcePackageRecipeData.findRecipes(
409+ self.repository, revspecs=list(set([self.path, self.name])))
410+ hook = SourcePackageRecipe.preLoadDataForSourcePackageRecipes
411+ return DecoratedResultSet(recipes, pre_iter_hook=hook)
412+
413
414 @implementer(IGitRef)
415 class GitRef(StormBase, GitRefMixin):
416
417=== modified file 'lib/lp/code/model/gitrepository.py'
418--- lib/lp/code/model/gitrepository.py 2015-12-10 00:05:41 +0000
419+++ lib/lp/code/model/gitrepository.py 2016-01-12 04:06:05 +0000
420@@ -1,4 +1,4 @@
421-# Copyright 2015 Canonical Ltd. This software is licensed under the
422+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
423 # GNU Affero General Public License version 3 (see the file LICENSE).
424
425 __metaclass__ = type
426@@ -130,6 +130,7 @@
427 DEFAULT,
428 UTC_NOW,
429 )
430+from lp.services.database.decoratedresultset import DecoratedResultSet
431 from lp.services.database.enumcol import EnumCol
432 from lp.services.database.interfaces import IStore
433 from lp.services.database.stormbase import StormBase
434@@ -953,6 +954,29 @@
435 jobs.append(UpdatePreviewDiffJob.create(merge_proposal))
436 return jobs
437
438+ def _getRecipes(self, paths=None):
439+ """Undecorated version of recipes for use by `markRecipesStale`."""
440+ from lp.code.model.sourcepackagerecipedata import (
441+ SourcePackageRecipeData,
442+ )
443+ if paths is not None:
444+ revspecs = set()
445+ for path in paths:
446+ revspecs.add(path)
447+ if path.startswith("refs/heads/"):
448+ revspecs.add(path[len("refs/heads/"):])
449+ revspecs = list(revspecs)
450+ else:
451+ revspecs = None
452+ return SourcePackageRecipeData.findRecipes(self, revspecs=revspecs)
453+
454+ @property
455+ def recipes(self):
456+ """See `IHasRecipes`."""
457+ from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
458+ hook = SourcePackageRecipe.preLoadDataForSourcePackageRecipes
459+ return DecoratedResultSet(self._getRecipes(), pre_iter_hook=hook)
460+
461 def _markProposalMerged(self, proposal, merged_revision_id, logger=None):
462 if logger is not None:
463 logger.info(
464
465=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
466--- lib/lp/code/model/sourcepackagerecipe.py 2015-09-28 17:38:45 +0000
467+++ lib/lp/code/model/sourcepackagerecipe.py 2016-01-12 04:06:05 +0000
468@@ -1,4 +1,4 @@
469-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
470+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
471 # GNU Affero General Public License version 3 (see the file LICENSE).
472
473 """Implementation of the `SourcePackageRecipe` content type."""
474@@ -13,6 +13,7 @@
475 timedelta,
476 )
477 from operator import attrgetter
478+import re
479
480 from lazr.delegates import delegate_to
481 from pytz import utc
482@@ -42,6 +43,7 @@
483 BuildNotAllowedForDistro,
484 )
485 from lp.code.interfaces.sourcepackagerecipe import (
486+ IRecipeBranchSource,
487 ISourcePackageRecipe,
488 ISourcePackageRecipeData,
489 ISourcePackageRecipeSource,
490@@ -150,6 +152,18 @@
491 def base_branch(self):
492 return self._recipe_data.base_branch
493
494+ @property
495+ def base_git_repository(self):
496+ return self._recipe_data.base_git_repository
497+
498+ @property
499+ def base(self):
500+ if self.base_branch is not None:
501+ return self.base_branch
502+ else:
503+ assert self.base_git_repository is not None
504+ return self.base_git_repository
505+
506 @staticmethod
507 def preLoadDataForSourcePackageRecipes(sourcepackagerecipes):
508 # Load the referencing SourcePackageRecipeData.
509@@ -167,12 +181,19 @@
510 owner_ids, need_validity=True))
511
512 def setRecipeText(self, recipe_text):
513- parsed = SourcePackageRecipeData.getParsedRecipe(recipe_text)
514+ parsed = getUtility(IRecipeBranchSource).getParsedRecipe(recipe_text)
515 self._recipe_data.setRecipe(parsed)
516
517 @property
518 def recipe_text(self):
519- return self.builder_recipe.get_recipe_text()
520+ recipe_text = self.builder_recipe.get_recipe_text()
521+ # For git-based recipes, mangle the header line to say
522+ # "git-build-recipe" to reduce confusion; bzr-builder's recipe
523+ # parser will always round-trip this to "bzr-builder".
524+ if self.base_git_repository is not None:
525+ recipe_text = re.sub(
526+ r"^(#\s*)bzr-builder", r"\1git-build-recipe", recipe_text)
527+ return recipe_text
528
529 def updateSeries(self, distroseries):
530 if distroseries != self.distroseries:
531@@ -187,7 +208,8 @@
532 """See `ISourcePackageRecipeSource.new`."""
533 store = IMasterStore(SourcePackageRecipe)
534 sprecipe = SourcePackageRecipe()
535- builder_recipe = SourcePackageRecipeData.getParsedRecipe(recipe)
536+ builder_recipe = getUtility(IRecipeBranchSource).getParsedRecipe(
537+ recipe)
538 SourcePackageRecipeData(builder_recipe, sprecipe)
539 sprecipe.registrant = registrant
540 sprecipe.owner = owner
541
542=== modified file 'lib/lp/code/model/sourcepackagerecipebuild.py'
543--- lib/lp/code/model/sourcepackagerecipebuild.py 2015-09-11 15:11:34 +0000
544+++ lib/lp/code/model/sourcepackagerecipebuild.py 2016-01-12 04:06:05 +0000
545@@ -1,4 +1,4 @@
546-# Copyright 2010-2013 Canonical Ltd. This software is licensed under the
547+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
548 # GNU Affero General Public License version 3 (see the file LICENSE).
549
550 """Implementation code for source package builds."""
551@@ -46,6 +46,10 @@
552 BuildAlreadyPending,
553 BuildNotAllowedForDistro,
554 )
555+from lp.code.interfaces.sourcepackagerecipe import (
556+ IRecipeBranchSource,
557+ ISourcePackageRecipeDataSource,
558+ )
559 from lp.code.interfaces.sourcepackagerecipebuild import (
560 ISourcePackageRecipeBuild,
561 ISourcePackageRecipeBuildSource,
562@@ -53,7 +57,6 @@
563 from lp.code.mail.sourcepackagerecipebuild import (
564 SourcePackageRecipeBuildMailer,
565 )
566-from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData
567 from lp.registry.interfaces.pocket import PackagePublishingPocket
568 from lp.registry.model.person import Person
569 from lp.services.database.bulk import load_related
570@@ -158,10 +161,11 @@
571 if self.manifest is not None:
572 IStore(self.manifest).remove(self.manifest)
573 elif self.manifest is None:
574- SourcePackageRecipeData.createManifestFromText(text, self)
575+ getUtility(ISourcePackageRecipeDataSource).createManifestFromText(
576+ text, self)
577 else:
578- from bzrlib.plugins.builder.recipe import RecipeParser
579- self.manifest.setRecipe(RecipeParser(text).parse())
580+ self.manifest.setRecipe(
581+ getUtility(IRecipeBranchSource).getParsedRecipe(text))
582
583 def getManifestText(self):
584 if self.manifest is None:
585@@ -179,7 +183,7 @@
586 if self.recipe is None:
587 branch_name = 'deleted'
588 else:
589- branch_name = self.recipe.base_branch.unique_name
590+ branch_name = self.recipe.base.unique_name
591 return '%s recipe build in %s %s' % (
592 branch_name, self.distribution.name, self.distroseries.name)
593
594
595=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
596--- lib/lp/code/model/sourcepackagerecipedata.py 2015-02-25 11:21:43 +0000
597+++ lib/lp/code/model/sourcepackagerecipedata.py 2016-01-12 04:06:05 +0000
598@@ -1,4 +1,4 @@
599-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
600+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
601 # GNU Affero General Public License version 3 (see the file LICENSE).
602
603 """Implementation of the recipe storage.
604@@ -12,6 +12,7 @@
605 __all__ = ['SourcePackageRecipeData']
606
607 from itertools import groupby
608+import re
609
610 from bzrlib.plugins.builder.recipe import (
611 BaseRecipeBranch,
612@@ -29,6 +30,7 @@
613 from storm.expr import Union
614 from storm.locals import (
615 And,
616+ In,
617 Int,
618 Reference,
619 ReferenceSet,
620@@ -38,14 +40,30 @@
621 Unicode,
622 )
623 from zope.component import getUtility
624+from zope.interface import (
625+ implementer,
626+ provider,
627+ )
628
629 from lp.code.errors import (
630 NoSuchBranch,
631+ NoSuchGitRepository,
632 PrivateBranchRecipe,
633+ PrivateGitRepositoryRecipe,
634 TooNewRecipeFormat,
635 )
636+from lp.code.interfaces.branch import IBranch
637 from lp.code.interfaces.branchlookup import IBranchLookup
638+from lp.code.interfaces.gitlookup import IGitLookup
639+from lp.code.interfaces.gitref import IGitRef
640+from lp.code.interfaces.gitrepository import IGitRepository
641+from lp.code.interfaces.sourcepackagerecipe import (
642+ IRecipeBranchSource,
643+ ISourcePackageRecipeData,
644+ ISourcePackageRecipeDataSource,
645+ )
646 from lp.code.model.branch import Branch
647+from lp.code.model.gitrepository import GitRepository
648 from lp.services.database.bulk import (
649 load_referencing,
650 load_related,
651@@ -83,15 +101,25 @@
652
653 __storm_table__ = "SourcePackageRecipeDataInstruction"
654
655- def __init__(self, name, type, comment, line_number, branch, revspec,
656- directory, recipe_data, parent_instruction,
657+ def __init__(self, name, type, comment, line_number, branch_or_repository,
658+ revspec, directory, recipe_data, parent_instruction,
659 source_directory):
660 super(_SourcePackageRecipeDataInstruction, self).__init__()
661 self.name = unicode(name)
662 self.type = type
663 self.comment = comment
664 self.line_number = line_number
665- self.branch = branch
666+ if IGitRepository.providedBy(branch_or_repository):
667+ self.git_repository = branch_or_repository
668+ elif IGitRef.providedBy(branch_or_repository):
669+ self.git_repository = branch_or_repository
670+ if revspec is None:
671+ revspec = branch_or_repository.name
672+ elif IBranch.providedBy(branch_or_repository):
673+ self.branch = branch_or_repository
674+ else:
675+ raise AssertionError(
676+ "Unsupported source: %r" % (branch_or_repository,))
677 if revspec is not None:
678 revspec = unicode(revspec)
679 self.revspec = revspec
680@@ -109,8 +137,10 @@
681 comment = Unicode(allow_none=True)
682 line_number = Int(allow_none=False)
683
684- branch_id = Int(name='branch', allow_none=False)
685+ branch_id = Int(name='branch', allow_none=True)
686 branch = Reference(branch_id, 'Branch.id')
687+ git_repository_id = Int(name='git_repository', allow_none=True)
688+ git_repository = Reference(git_repository_id, 'GitRepository.id')
689
690 revspec = Unicode(allow_none=True)
691 directory = Unicode(allow_none=True)
692@@ -125,8 +155,12 @@
693
694 def appendToRecipe(self, recipe_branch):
695 """Append a bzr-builder instruction to the recipe_branch object."""
696- branch = RecipeBranch(
697- self.name, self.branch.bzr_identity, self.revspec)
698+ if self.branch is not None:
699+ identity = self.branch.identity
700+ else:
701+ assert self.git_repository is not None
702+ identity = self.git_repository.identity
703+ branch = RecipeBranch(self.name, identity, self.revspec)
704 if self.type == InstructionType.MERGE:
705 recipe_branch.merge_branch(branch)
706 elif self.type == InstructionType.NEST:
707@@ -142,6 +176,8 @@
708 MAX_RECIPE_FORMAT = 0.4
709
710
711+@implementer(ISourcePackageRecipeData)
712+@provider(IRecipeBranchSource, ISourcePackageRecipeDataSource)
713 class SourcePackageRecipeData(Storm):
714 """The database representation of a BaseRecipeBranch from bzr-builder.
715
716@@ -154,8 +190,29 @@
717
718 id = Int(primary=True)
719
720- base_branch_id = Int(name='base_branch', allow_none=False)
721+ base_branch_id = Int(name='base_branch', allow_none=True)
722 base_branch = Reference(base_branch_id, 'Branch.id')
723+ base_git_repository_id = Int(name='base_git_repository', allow_none=True)
724+ base_git_repository = Reference(base_git_repository_id, 'GitRepository.id')
725+
726+ @property
727+ def base(self):
728+ if self.base_branch is not None:
729+ return self.base_branch
730+ else:
731+ assert self.base_git_repository is not None
732+ return self.base_git_repository
733+
734+ @base.setter
735+ def base(self, value):
736+ if IGitRepository.providedBy(value):
737+ self.base_git_repository = value
738+ self.base_branch = None
739+ elif IBranch.providedBy(value):
740+ self.base_branch = value
741+ self.base_git_repository = None
742+ else:
743+ raise AssertionError("Unsupported base: %r" % (value,))
744
745 recipe_format = Unicode(allow_none=False)
746 deb_version_template = Unicode(allow_none=True)
747@@ -177,38 +234,68 @@
748
749 @staticmethod
750 def getParsedRecipe(recipe_text):
751+ """See `IRecipeBranchSource`."""
752+ # We're using bzr-builder to parse the recipe text. While the
753+ # formats are mostly compatible, the header line must say
754+ # "bzr-builder" even though git-build-recipe also supports its own
755+ # name there.
756+ recipe_text = re.sub(
757+ r"^(#\s*)git-build-recipe", r"\1bzr-builder", recipe_text)
758 parser = RecipeParser(recipe_text)
759 return parser.parse(permitted_instructions=SAFE_INSTRUCTIONS)
760
761 @staticmethod
762- def findRecipes(branch):
763+ def findRecipes(branch_or_repository, revspecs=None):
764+ """Find recipes for a given branch or repository.
765+
766+ :param branch_or_repository: The branch or repository to search for.
767+ :param revspecs: If not None, return only recipes whose `revspec` is
768+ in this sequence.
769+ :return: a collection of `ISourcePackageRecipe`s.
770+ """
771 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
772- store = Store.of(branch)
773+ store = Store.of(branch_or_repository)
774+ if IGitRepository.providedBy(branch_or_repository):
775+ data_clause = (
776+ SourcePackageRecipeData.base_git_repository ==
777+ branch_or_repository)
778+ insn_clause = (
779+ _SourcePackageRecipeDataInstruction.git_repository ==
780+ branch_or_repository)
781+ elif IBranch.providedBy(branch_or_repository):
782+ data_clause = (
783+ SourcePackageRecipeData.base_branch == branch_or_repository)
784+ insn_clause = (
785+ _SourcePackageRecipeDataInstruction.branch ==
786+ branch_or_repository)
787+ else:
788+ raise AssertionError(
789+ "Unsupported source: %r" % (branch_or_repository,))
790+ if revspecs is not None:
791+ data_clause = And(
792+ data_clause, In(SourcePackageRecipeData.revspec, revspecs))
793+ insn_clause = And(
794+ insn_clause,
795+ In(_SourcePackageRecipeDataInstruction.revspec, revspecs))
796 return store.find(
797 SourcePackageRecipe,
798 SourcePackageRecipe.id.is_in(Union(
799 Select(
800 SourcePackageRecipeData.sourcepackage_recipe_id,
801- SourcePackageRecipeData.base_branch == branch),
802+ data_clause),
803 Select(
804 SourcePackageRecipeData.sourcepackage_recipe_id,
805 And(
806 _SourcePackageRecipeDataInstruction.recipe_data_id ==
807 SourcePackageRecipeData.id,
808- _SourcePackageRecipeDataInstruction.branch == branch)
809+ insn_clause)
810 )
811 ))
812 )
813
814 @classmethod
815 def createManifestFromText(cls, text, sourcepackage_recipe_build):
816- """Create a manifest for the specified build.
817-
818- :param text: The text of the recipe to create a manifest for.
819- :param sourcepackage_recipe_build: The build to associate the manifest
820- with.
821- :return: an instance of SourcePackageRecipeData.
822- """
823+ """See `ISourcePackageRecipeDataSource`."""
824 parsed = cls.getParsedRecipe(text)
825 return cls(
826 parsed, sourcepackage_recipe_build=sourcepackage_recipe_build)
827@@ -216,7 +303,7 @@
828 def getRecipe(self):
829 """The BaseRecipeBranch version of the recipe."""
830 base_branch = BaseRecipeBranch(
831- self.base_branch.bzr_identity, self.deb_version_template,
832+ self.base.identity, self.deb_version_template,
833 self.recipe_format, self.revspec)
834 insn_stack = []
835 for insn in self.instructions:
836@@ -232,7 +319,7 @@
837 dict(insn=insn, recipe_branch=recipe_branch))
838 return base_branch
839
840- def _scanInstructions(self, recipe_branch):
841+ def _scanInstructions(self, base, recipe_branch):
842 """Check the recipe_branch doesn't use 'run' and look up the branches.
843
844 We do all the lookups before we start constructing database objects to
845@@ -241,15 +328,22 @@
846 :return: A map ``{branch_url: db_branch}``.
847 """
848 r = {}
849+ if IGitRepository.providedBy(base):
850+ lookup = getUtility(IGitLookup)
851+ missing_error = NoSuchGitRepository
852+ private_error = PrivateGitRepositoryRecipe
853+ else:
854+ lookup = getUtility(IBranchLookup)
855+ missing_error = NoSuchBranch
856+ private_error = PrivateBranchRecipe
857 for instruction in recipe_branch.child_branches:
858- db_branch = getUtility(IBranchLookup).getByUrl(
859- instruction.recipe_branch.url)
860+ db_branch = lookup.getByUrl(instruction.recipe_branch.url)
861 if db_branch is None:
862- raise NoSuchBranch(instruction.recipe_branch.url)
863+ raise missing_error(instruction.recipe_branch.url)
864 if db_branch.private:
865- raise PrivateBranchRecipe(db_branch)
866+ raise private_error(db_branch)
867 r[instruction.recipe_branch.url] = db_branch
868- r.update(self._scanInstructions(instruction.recipe_branch))
869+ r.update(self._scanInstructions(base, instruction.recipe_branch))
870 return r
871
872 def _recordInstructions(self, recipe_branch, parent_insn, branch_map,
873@@ -274,10 +368,11 @@
874 "Unsupported instruction %r" % instruction)
875 line_number += 1
876 comment = None
877- db_branch = branch_map[instruction.recipe_branch.url]
878+ db_branch_or_repository = branch_map[instruction.recipe_branch.url]
879 insn = _SourcePackageRecipeDataInstruction(
880 instruction.recipe_branch.name, type, comment,
881- line_number, db_branch, instruction.recipe_branch.revspec,
882+ line_number, db_branch_or_repository,
883+ instruction.recipe_branch.revspec,
884 nest_path, self, parent_insn, source_directory)
885 line_number = self._recordInstructions(
886 instruction.recipe_branch, insn, branch_map, line_number)
887@@ -288,24 +383,33 @@
888 clear_property_cache(self)
889 if builder_recipe.format > MAX_RECIPE_FORMAT:
890 raise TooNewRecipeFormat(builder_recipe.format, MAX_RECIPE_FORMAT)
891- branch_map = self._scanInstructions(builder_recipe)
892+ base = getUtility(IBranchLookup).getByUrl(builder_recipe.url)
893+ if base is None:
894+ base = getUtility(IGitLookup).getByUrl(builder_recipe.url)
895+ if base is None:
896+ # If possible, try to raise an exception consistent with
897+ # whether the current recipe is Bazaar-based or Git-based,
898+ # so that error messages make more sense.
899+ if self.base_git_repository is not None:
900+ raise NoSuchGitRepository(builder_recipe.url)
901+ else:
902+ raise NoSuchBranch(builder_recipe.url)
903+ elif base.private:
904+ raise PrivateGitRepositoryRecipe(base)
905+ elif base.private:
906+ raise PrivateBranchRecipe(base)
907+ branch_map = self._scanInstructions(base, builder_recipe)
908 # If this object hasn't been added to a store yet, there can't be any
909 # instructions linking to us yet.
910 if Store.of(self) is not None:
911 self.instructions.find().remove()
912- branch_lookup = getUtility(IBranchLookup)
913- base_branch = branch_lookup.getByUrl(builder_recipe.url)
914- if base_branch is None:
915- raise NoSuchBranch(builder_recipe.url)
916- if base_branch.private:
917- raise PrivateBranchRecipe(base_branch)
918 if builder_recipe.revspec is not None:
919 self.revspec = unicode(builder_recipe.revspec)
920 else:
921 self.revspec = None
922 self._recordInstructions(
923 builder_recipe, parent_insn=None, branch_map=branch_map)
924- self.base_branch = base_branch
925+ self.base = base
926 if builder_recipe.deb_version is None:
927 self.deb_version_template = None
928 else:
929@@ -323,44 +427,72 @@
930
931 @staticmethod
932 def preLoadReferencedBranches(sourcepackagerecipedatas):
933- # Circular import.
934+ # Circular imports.
935 from lp.code.model.branchcollection import GenericBranchCollection
936+ from lp.code.model.gitcollection import GenericGitCollection
937 # Load the related Branch, _SourcePackageRecipeDataInstruction.
938 base_branches = load_related(
939 Branch, sourcepackagerecipedatas, ['base_branch_id'])
940+ base_repositories = load_related(
941+ GitRepository, sourcepackagerecipedatas,
942+ ['base_git_repository_id'])
943 sprd_instructions = load_referencing(
944 _SourcePackageRecipeDataInstruction,
945 sourcepackagerecipedatas, ['recipe_data_id'])
946 sub_branches = load_related(
947 Branch, sprd_instructions, ['branch_id'])
948+ sub_repositories = load_related(
949+ GitRepository, sprd_instructions, ['git_repository_id'])
950 all_branches = base_branches + sub_branches
951- # Pre-load branches' data.
952- GenericBranchCollection.preloadDataForBranches(all_branches)
953+ all_repositories = base_repositories + sub_repositories
954+ # Pre-load branches'/repositories' data.
955+ if all_branches:
956+ GenericBranchCollection.preloadDataForBranches(all_branches)
957+ if all_repositories:
958+ GenericGitCollection.preloadDataForRepositories(all_repositories)
959 # Store the pre-fetched objects on the sourcepackagerecipedatas
960 # objects.
961- branch_to_recipe_data = dict([
962- (instr.branch_id, instr.recipe_data_id)
963- for instr in sprd_instructions])
964- caches = dict((sprd.id, [sprd, get_property_cache(sprd)])
965- for sprd in sourcepackagerecipedatas)
966+ branch_to_recipe_data = {
967+ instr.branch_id: instr.recipe_data_id
968+ for instr in sprd_instructions
969+ if instr.branch_id is not None}
970+ repository_to_recipe_data = {
971+ instr.git_repository_id: instr.recipe_data_id
972+ for instr in sprd_instructions
973+ if instr.git_repository_id is not None}
974+ caches = {
975+ sprd.id: [sprd, get_property_cache(sprd)]
976+ for sprd in sourcepackagerecipedatas}
977 for unused, [sprd, cache] in caches.items():
978- cache._referenced_branches = [sprd.base_branch]
979+ cache._referenced_branches = [sprd.base]
980 for recipe_data_id, branches in groupby(
981- sub_branches, lambda branch: branch_to_recipe_data[branch.id]):
982+ sub_branches, lambda branch: branch_to_recipe_data[branch.id]):
983 cache = caches[recipe_data_id][1]
984 cache._referenced_branches.extend(list(branches))
985+ for recipe_data_id, repositories in groupby(
986+ sub_repositories,
987+ lambda repository: repository_to_recipe_data[repository.id]):
988+ cache = caches[recipe_data_id][1]
989+ cache._referenced_branches.extend(list(repositories))
990
991 def getReferencedBranches(self):
992- """Return an iterator of the Branch objects referenced by this recipe.
993+ """Return an iterator of the Branch/GitRepository objects referenced
994+ by this recipe.
995 """
996 return self._referenced_branches
997
998 @cachedproperty
999 def _referenced_branches(self):
1000- referenced_branches = [self.base_branch]
1001+ referenced_branches = [self.base]
1002 sub_branches = IStore(self).find(
1003 Branch,
1004 _SourcePackageRecipeDataInstruction.recipe_data == self,
1005 Branch.id == _SourcePackageRecipeDataInstruction.branch_id)
1006 referenced_branches.extend(sub_branches)
1007+ sub_repositories = IStore(self).find(
1008+ GitRepository,
1009+ _SourcePackageRecipeDataInstruction.recipe_data == self,
1010+ GitRepository.id ==
1011+ _SourcePackageRecipeDataInstruction.git_repository_id)
1012+ referenced_branches.extend(sub_repositories)
1013 return referenced_branches
1014
1015=== modified file 'lib/lp/code/model/tests/test_hasrecipes.py'
1016--- lib/lp/code/model/tests/test_hasrecipes.py 2015-09-16 13:26:12 +0000
1017+++ lib/lp/code/model/tests/test_hasrecipes.py 2016-01-12 04:06:05 +0000
1018@@ -1,4 +1,4 @@
1019-# Copyright 2010-2015 Canonical Ltd. This software is licensed under the
1020+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
1021 # GNU Affero General Public License version 3 (see the file LICENSE).
1022
1023 """Tests for classes that implement IHasRecipes."""
1024@@ -39,6 +39,33 @@
1025 self.factory.makeSourcePackageRecipe()
1026 self.assertEqual(recipe, nonbase_branch.recipes.one())
1027
1028+ def test_git_repository_implements_hasrecipes(self):
1029+ # Git repositories should implement IHasRecipes.
1030+ repository = self.factory.makeGitRepository()
1031+ self.assertProvides(repository, IHasRecipes)
1032+
1033+ def test_git_repository_recipes(self):
1034+ # IGitRepository.recipes should provide all the SourcePackageRecipes
1035+ # attached to that repository.
1036+ base_ref1, base_ref2 = self.factory.makeGitRefs(
1037+ paths=[u"refs/heads/ref1", u"refs/heads/ref2"])
1038+ [other_ref] = self.factory.makeGitRefs()
1039+ self.factory.makeSourcePackageRecipe(branches=[base_ref1])
1040+ self.factory.makeSourcePackageRecipe(branches=[base_ref2])
1041+ self.factory.makeSourcePackageRecipe(branches=[other_ref])
1042+ self.assertEqual(2, base_ref1.repository.recipes.count())
1043+
1044+ def test_git_repository_recipes_nonbase(self):
1045+ # IGitRepository.recipes should provide all the SourcePackageRecipes
1046+ # that refer to the repository, even as a non-base branch.
1047+ [base_ref] = self.factory.makeGitRefs()
1048+ [nonbase_ref] = self.factory.makeGitRefs()
1049+ [other_ref] = self.factory.makeGitRefs()
1050+ recipe = self.factory.makeSourcePackageRecipe(
1051+ branches=[base_ref, nonbase_ref])
1052+ self.factory.makeSourcePackageRecipe(branches=[other_ref])
1053+ self.assertEqual(recipe, nonbase_ref.repository.recipes.one())
1054+
1055 def test_person_implements_hasrecipes(self):
1056 # Person should implement IHasRecipes.
1057 person = self.factory.makePerson()
1058
1059=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
1060--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2015-09-12 00:23:59 +0000
1061+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2016-01-12 04:06:05 +0000
1062@@ -32,13 +32,15 @@
1063 from lp.code.errors import (
1064 BuildAlreadyPending,
1065 PrivateBranchRecipe,
1066+ PrivateGitRepositoryRecipe,
1067 TooNewRecipeFormat,
1068 )
1069 from lp.code.interfaces.sourcepackagerecipe import (
1070 ISourcePackageRecipe,
1071 ISourcePackageRecipeSource,
1072 ISourcePackageRecipeView,
1073- MINIMAL_RECIPE_TEXT,
1074+ MINIMAL_RECIPE_TEXT_BZR,
1075+ MINIMAL_RECIPE_TEXT_GIT,
1076 )
1077 from lp.code.interfaces.sourcepackagerecipebuild import (
1078 ISourcePackageRecipeBuild,
1079@@ -86,14 +88,76 @@
1080 from lp.testing.pages import webservice_for_person
1081
1082
1083-class TestSourcePackageRecipe(TestCaseWithFactory):
1084+class BzrMixin:
1085+ """Mixin for Bazaar-based recipe tests."""
1086+
1087+ private_error = PrivateBranchRecipe
1088+ branch_type = "branch"
1089+ recipe_id = "bzr-builder"
1090+
1091+ def makeBranch(self, **kwargs):
1092+ return self.factory.makeAnyBranch(**kwargs)
1093+
1094+ @staticmethod
1095+ def getRepository(branch):
1096+ return branch
1097+
1098+ @staticmethod
1099+ def getBranchRecipeText(branch):
1100+ return branch.identity
1101+
1102+ @staticmethod
1103+ def setInformationType(branch, information_type):
1104+ removeSecurityProxy(branch).information_type = information_type
1105+
1106+ def makeRecipeText(self):
1107+ branch = self.makeBranch()
1108+ return MINIMAL_RECIPE_TEXT_BZR % branch.identity
1109+
1110+
1111+class GitMixin:
1112+ """Mixin for Git-based recipe tests."""
1113+
1114+ private_error = PrivateGitRepositoryRecipe
1115+ branch_type = "repository"
1116+ recipe_id = "git-build-recipe"
1117+
1118+ def makeBranch(self, **kwargs):
1119+ return self.factory.makeGitRefs(**kwargs)[0]
1120+
1121+ @staticmethod
1122+ def getRepository(branch):
1123+ return branch.repository
1124+
1125+ @staticmethod
1126+ def getBranchRecipeText(branch):
1127+ return branch.identity
1128+
1129+ @staticmethod
1130+ def setInformationType(branch, information_type):
1131+ removeSecurityProxy(branch.repository).information_type = (
1132+ information_type)
1133+
1134+ def makeRecipeText(self):
1135+ branch = self.makeBranch()
1136+ return MINIMAL_RECIPE_TEXT_GIT % (
1137+ branch.repository.identity, branch.name)
1138+
1139+
1140+class TestSourcePackageRecipeMixin:
1141 """Tests for `SourcePackageRecipe` objects."""
1142
1143 layer = DatabaseFunctionalLayer
1144
1145+ def makeSourcePackageRecipe(self, branches=(), recipe=None, **kwargs):
1146+ if recipe is None and len(branches) == 0:
1147+ branches = [self.makeBranch()]
1148+ return self.factory.makeSourcePackageRecipe(
1149+ branches=branches, recipe=recipe, **kwargs)
1150+
1151 def test_implements_interface(self):
1152 """SourcePackageRecipe implements ISourcePackageRecipe."""
1153- recipe = self.factory.makeSourcePackageRecipe()
1154+ recipe = self.makeSourcePackageRecipe()
1155 verifyObject(ISourcePackageRecipe, recipe)
1156
1157 def test_avoids_problematic_snapshots(self):
1158@@ -103,7 +167,7 @@
1159 'pending_builds',
1160 ]
1161 self.assertThat(
1162- self.factory.makeSourcePackageRecipe(),
1163+ self.makeSourcePackageRecipe(),
1164 DoesNotSnapshot(problematic_properties, ISourcePackageRecipeView))
1165
1166 def makeRecipeComponents(self, branches=()):
1167@@ -128,7 +192,7 @@
1168 components = self.makeRecipeComponents()
1169 recipe = getUtility(ISourcePackageRecipeSource).new(**components)
1170 transaction.commit()
1171- self.assertEquals(
1172+ self.assertEqual(
1173 (components['registrant'], components['owner'],
1174 set(components['distroseries']), components['name']),
1175 (recipe.registrant, recipe.owner, set(recipe.distroseries),
1176@@ -139,35 +203,38 @@
1177 """An exception should be raised if the base branch is private."""
1178 owner = self.factory.makePerson()
1179 with person_logged_in(owner):
1180- branch = self.factory.makeAnyBranch(
1181+ branch = self.makeBranch(
1182 owner=owner, information_type=InformationType.USERDATA)
1183 components = self.makeRecipeComponents(branches=[branch])
1184 recipe_source = getUtility(ISourcePackageRecipeSource)
1185 e = self.assertRaises(
1186- PrivateBranchRecipe, recipe_source.new, **components)
1187+ self.private_error, recipe_source.new, **components)
1188 self.assertEqual(
1189- 'Recipe may not refer to private branch: %s' %
1190- branch.bzr_identity, str(e))
1191+ 'Recipe may not refer to private %s: %s' %
1192+ (self.branch_type, self.getRepository(branch).identity),
1193+ str(e))
1194
1195 def test_creation_private_referenced_branch(self):
1196 """An exception should be raised if a referenced branch is private."""
1197 owner = self.factory.makePerson()
1198 with person_logged_in(owner):
1199- base_branch = self.factory.makeAnyBranch(owner=owner)
1200- referenced_branch = self.factory.makeAnyBranch(
1201+ base_branch = self.makeBranch(owner=owner)
1202+ referenced_branch = self.makeBranch(
1203 owner=owner, information_type=InformationType.USERDATA)
1204 branches = [base_branch, referenced_branch]
1205 components = self.makeRecipeComponents(branches=branches)
1206 recipe_source = getUtility(ISourcePackageRecipeSource)
1207 e = self.assertRaises(
1208- PrivateBranchRecipe, recipe_source.new, **components)
1209+ self.private_error, recipe_source.new, **components)
1210 self.assertEqual(
1211- 'Recipe may not refer to private branch: %s' %
1212- referenced_branch.bzr_identity, str(e))
1213+ 'Recipe may not refer to private %s: %s' % (
1214+ self.branch_type,
1215+ self.getRepository(referenced_branch).identity),
1216+ str(e))
1217
1218 def test_exists(self):
1219 # Test ISourcePackageRecipeSource.exists
1220- recipe = self.factory.makeSourcePackageRecipe()
1221+ recipe = self.makeSourcePackageRecipe()
1222
1223 self.assertTrue(
1224 getUtility(ISourcePackageRecipeSource).exists(
1225@@ -185,32 +252,33 @@
1226
1227 def test_recipe_implements_interface(self):
1228 # SourcePackageRecipe objects implement ISourcePackageRecipe.
1229- recipe = self.factory.makeSourcePackageRecipe()
1230+ recipe = self.makeSourcePackageRecipe()
1231 transaction.commit()
1232 with person_logged_in(recipe.owner):
1233 self.assertProvides(recipe, ISourcePackageRecipe)
1234
1235 def test_base_branch(self):
1236 # When a recipe is created, we can access its base branch.
1237- branch = self.factory.makeAnyBranch()
1238- sp_recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
1239+ branch = self.makeBranch()
1240+ sp_recipe = self.makeSourcePackageRecipe(branches=[branch])
1241 transaction.commit()
1242- self.assertEquals(branch, sp_recipe.base_branch)
1243+ self.assertEqual(self.getRepository(branch), sp_recipe.base)
1244
1245 def test_branch_links_created(self):
1246 # When a recipe is created, we can query it for links to the branch
1247 # it references.
1248- branch = self.factory.makeAnyBranch()
1249- sp_recipe = self.factory.makeSourcePackageRecipe(
1250- branches=[branch])
1251+ branch = self.makeBranch()
1252+ sp_recipe = self.makeSourcePackageRecipe(branches=[branch])
1253 transaction.commit()
1254- self.assertEquals([branch], list(sp_recipe.getReferencedBranches()))
1255+ self.assertEqual(
1256+ [self.getRepository(branch)],
1257+ list(sp_recipe.getReferencedBranches()))
1258
1259 def createSourcePackageRecipe(self, number_of_branches=2):
1260 branches = []
1261 for i in range(number_of_branches):
1262- branches.append(self.factory.makeAnyBranch())
1263- sp_recipe = self.factory.makeSourcePackageRecipe(branches=branches)
1264+ branches.append(self.makeBranch())
1265+ sp_recipe = self.makeSourcePackageRecipe(branches=branches)
1266 transaction.commit()
1267 return sp_recipe, branches
1268
1269@@ -218,8 +286,8 @@
1270 # If a recipe links to more than one branch, getReferencedBranches()
1271 # returns all of them.
1272 sp_recipe, [branch1, branch2] = self.createSourcePackageRecipe()
1273- self.assertEquals(
1274- sorted([branch1, branch2]),
1275+ self.assertEqual(
1276+ sorted([self.getRepository(branch1), self.getRepository(branch2)]),
1277 sorted(sp_recipe.getReferencedBranches()))
1278
1279 def test_preLoadReferencedBranches(self):
1280@@ -230,16 +298,15 @@
1281 referenced_branches = sp_recipe.getReferencedBranches()
1282 clear_property_cache(recipe_data)
1283 SourcePackageRecipeData.preLoadReferencedBranches([recipe_data])
1284- self.assertEquals(
1285+ self.assertEqual(
1286 sorted(referenced_branches),
1287 sorted(sp_recipe.getReferencedBranches()))
1288
1289 def test_random_user_cant_edit(self):
1290 # An arbitrary user can't set attributes.
1291- branch1 = self.factory.makeAnyBranch()
1292+ branch1 = self.makeBranch()
1293 recipe_1 = self.factory.makeRecipeText(branch1)
1294- sp_recipe = self.factory.makeSourcePackageRecipe(
1295- recipe=recipe_1)
1296+ sp_recipe = self.makeSourcePackageRecipe(recipe=recipe_1)
1297 login_person(self.factory.makePerson())
1298 self.assertRaises(
1299 Unauthorized, getattr, sp_recipe, 'setRecipeText')
1300@@ -247,71 +314,78 @@
1301 def test_set_recipe_text_resets_branch_references(self):
1302 # When the recipe_text is replaced, getReferencedBranches returns
1303 # (only) the branches referenced by the new recipe.
1304- branch1 = self.factory.makeAnyBranch()
1305- sp_recipe = self.factory.makeSourcePackageRecipe(
1306- branches=[branch1])
1307- branch2 = self.factory.makeAnyBranch()
1308+ branch1 = self.makeBranch()
1309+ sp_recipe = self.makeSourcePackageRecipe(branches=[branch1])
1310+ branch2 = self.makeBranch()
1311 new_recipe = self.factory.makeRecipeText(branch2)
1312 with person_logged_in(sp_recipe.owner):
1313 sp_recipe.setRecipeText(new_recipe)
1314- self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))
1315+ self.assertEqual(
1316+ [self.getRepository(branch2)],
1317+ list(sp_recipe.getReferencedBranches()))
1318
1319 def test_rejects_run_command(self):
1320 recipe_text = '''\
1321- # bzr-builder format 0.3 deb-version 0.1-{revno}
1322+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1323 %(base)s
1324 run touch test
1325- ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
1326+ ''' % dict(recipe_id=self.recipe_id,
1327+ base=self.getBranchRecipeText(self.makeBranch()))
1328 recipe_text = textwrap.dedent(recipe_text)
1329 self.assertRaises(
1330- ForbiddenInstructionError, self.factory.makeSourcePackageRecipe,
1331+ ForbiddenInstructionError, self.makeSourcePackageRecipe,
1332 recipe=recipe_text)
1333
1334 def test_run_rejected_without_mangling_recipe(self):
1335- sp_recipe = self.factory.makeSourcePackageRecipe()
1336+ sp_recipe = self.makeSourcePackageRecipe()
1337 old_branches = list(sp_recipe.getReferencedBranches())
1338 recipe_text = '''\
1339- # bzr-builder format 0.3 deb-version 0.1-{revno}
1340+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1341 %(base)s
1342 run touch test
1343- ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
1344+ ''' % dict(recipe_id=self.recipe_id,
1345+ base=self.getBranchRecipeText(self.makeBranch()))
1346 recipe_text = textwrap.dedent(recipe_text)
1347 with person_logged_in(sp_recipe.owner):
1348 self.assertRaises(
1349 ForbiddenInstructionError, sp_recipe.setRecipeText,
1350 recipe_text)
1351- self.assertEquals(
1352+ self.assertEqual(
1353 old_branches, list(sp_recipe.getReferencedBranches()))
1354
1355 def test_nest_part(self):
1356 """nest-part instruction can be round-tripped."""
1357- base = self.factory.makeBranch()
1358- nested = self.factory.makeBranch()
1359+ base = self.makeBranch()
1360+ nested = self.makeBranch()
1361 recipe_text = (
1362- "# bzr-builder format 0.3 deb-version 1\n"
1363+ "# %s format 0.3 deb-version 1\n"
1364 "%s revid:base_revid\n"
1365 "nest-part nested1 %s foo bar tag:foo\n" %
1366- (base.bzr_identity, nested.bzr_identity))
1367- recipe = self.factory.makeSourcePackageRecipe(recipe=recipe_text)
1368+ (self.recipe_id,
1369+ self.getRepository(base).identity,
1370+ self.getRepository(nested).identity))
1371+ recipe = self.makeSourcePackageRecipe(recipe=recipe_text)
1372 self.assertEqual(recipe_text, recipe.recipe_text)
1373
1374 def test_nest_part_no_target(self):
1375 """nest-part instruction with no target-dir can be round-tripped."""
1376- base = self.factory.makeBranch()
1377- nested = self.factory.makeBranch()
1378+ base = self.makeBranch()
1379+ nested = self.makeBranch()
1380 recipe_text = (
1381- "# bzr-builder format 0.3 deb-version 1\n"
1382+ "# %s format 0.3 deb-version 1\n"
1383 "%s revid:base_revid\n"
1384 "nest-part nested1 %s foo\n" %
1385- (base.bzr_identity, nested.bzr_identity))
1386- recipe = self.factory.makeSourcePackageRecipe(recipe=recipe_text)
1387+ (self.recipe_id,
1388+ self.getRepository(base).identity,
1389+ self.getRepository(nested).identity))
1390+ recipe = self.makeSourcePackageRecipe(recipe=recipe_text)
1391 self.assertEqual(recipe_text, recipe.recipe_text)
1392
1393 def test_accept_format_0_3(self):
1394 """Recipe format 0.3 is accepted."""
1395 builder_recipe = self.factory.makeRecipe()
1396 builder_recipe.format = 0.3
1397- self.factory.makeSourcePackageRecipe(recipe=str(builder_recipe))
1398+ self.makeSourcePackageRecipe(recipe=str(builder_recipe))
1399
1400 def test_reject_newer_formats(self):
1401 with recipe_parser_newest_version(145.115):
1402@@ -319,11 +393,11 @@
1403 builder_recipe.format = 145.115
1404 self.assertRaises(
1405 TooNewRecipeFormat,
1406- self.factory.makeSourcePackageRecipe,
1407+ self.makeSourcePackageRecipe,
1408 recipe=str(builder_recipe))
1409
1410 def test_requestBuild(self):
1411- recipe = self.factory.makeSourcePackageRecipe()
1412+ recipe = self.makeSourcePackageRecipe()
1413 (distroseries,) = list(recipe.distroseries)
1414 ppa = self.factory.makeArchive()
1415 build = recipe.requestBuild(ppa, ppa.owner, distroseries,
1416@@ -342,17 +416,17 @@
1417 removeSecurityProxy(build).build_farm_job_id).one()
1418 self.assertProvides(build_queue, IBuildQueue)
1419 self.assertTrue(build_queue.virtualized)
1420- self.assertEquals(build_queue.status, BuildQueueStatus.WAITING)
1421+ self.assertEqual(build_queue.status, BuildQueueStatus.WAITING)
1422
1423 def test_requestBuildRejectsNotPPA(self):
1424- recipe = self.factory.makeSourcePackageRecipe()
1425+ recipe = self.makeSourcePackageRecipe()
1426 not_ppa = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
1427 (distroseries,) = list(recipe.distroseries)
1428 self.assertRaises(NonPPABuildRequest, recipe.requestBuild, not_ppa,
1429 not_ppa.owner, distroseries, PackagePublishingPocket.RELEASE)
1430
1431 def test_requestBuildRejectsNoPermission(self):
1432- recipe = self.factory.makeSourcePackageRecipe()
1433+ recipe = self.makeSourcePackageRecipe()
1434 ppa = self.factory.makeArchive()
1435 requester = self.factory.makePerson()
1436 (distroseries,) = list(recipe.distroseries)
1437@@ -360,14 +434,14 @@
1438 requester, distroseries, PackagePublishingPocket.RELEASE)
1439
1440 def test_requestBuildRejectsInvalidPocket(self):
1441- recipe = self.factory.makeSourcePackageRecipe()
1442+ recipe = self.makeSourcePackageRecipe()
1443 ppa = self.factory.makeArchive()
1444 (distroseries,) = list(recipe.distroseries)
1445 self.assertRaises(InvalidPocketForPPA, recipe.requestBuild, ppa,
1446 ppa.owner, distroseries, PackagePublishingPocket.BACKPORTS)
1447
1448 def test_requestBuildRejectsDisabledArchive(self):
1449- recipe = self.factory.makeSourcePackageRecipe()
1450+ recipe = self.makeSourcePackageRecipe()
1451 ppa = self.factory.makeArchive()
1452 removeSecurityProxy(ppa).disable()
1453 (distroseries,) = list(recipe.distroseries)
1454@@ -377,7 +451,7 @@
1455
1456 def test_requestBuildScore(self):
1457 """Normal build requests have a relatively low queue score (2505)."""
1458- recipe = self.factory.makeSourcePackageRecipe()
1459+ recipe = self.makeSourcePackageRecipe()
1460 build = recipe.requestBuild(recipe.daily_build_archive,
1461 recipe.owner, list(recipe.distroseries)[0],
1462 PackagePublishingPocket.RELEASE)
1463@@ -387,7 +461,7 @@
1464
1465 def test_requestBuildManualScore(self):
1466 """Manual build requests have a score equivalent to binary builds."""
1467- recipe = self.factory.makeSourcePackageRecipe()
1468+ recipe = self.makeSourcePackageRecipe()
1469 build = recipe.requestBuild(recipe.daily_build_archive,
1470 recipe.owner, list(recipe.distroseries)[0],
1471 PackagePublishingPocket.RELEASE, manual=True)
1472@@ -397,7 +471,7 @@
1473
1474 def test_requestBuild_relative_build_score(self):
1475 """Offsets for archives are respected."""
1476- recipe = self.factory.makeSourcePackageRecipe()
1477+ recipe = self.makeSourcePackageRecipe()
1478 archive = recipe.daily_build_archive
1479 removeSecurityProxy(archive).relative_build_score = 100
1480 build = recipe.requestBuild(
1481@@ -409,7 +483,7 @@
1482
1483 def test_requestBuildRejectRepeats(self):
1484 """Reject build requests that are identical to pending builds."""
1485- recipe = self.factory.makeSourcePackageRecipe()
1486+ recipe = self.makeSourcePackageRecipe()
1487 series = list(recipe.distroseries)[0]
1488 archive = self.factory.makeArchive(owner=recipe.owner)
1489 old_build = recipe.requestBuild(archive, recipe.owner, series,
1490@@ -447,7 +521,7 @@
1491 private=True)
1492
1493 # Create a recipe with the team P3A as the build destination.
1494- recipe = self.factory.makeSourcePackageRecipe()
1495+ recipe = self.makeSourcePackageRecipe()
1496
1497 # Add upload component rights for the non-team person.
1498 with person_logged_in(team_owner):
1499@@ -467,13 +541,13 @@
1500 def test_sourcepackagerecipe_description(self):
1501 """Ensure that the SourcePackageRecipe has a proper description."""
1502 description = u'The whoozits and whatzits.'
1503- source_package_recipe = self.factory.makeSourcePackageRecipe(
1504+ source_package_recipe = self.makeSourcePackageRecipe(
1505 description=description)
1506 self.assertEqual(description, source_package_recipe.description)
1507
1508 def test_distroseries(self):
1509 """Test that the distroseries behaves as a set."""
1510- recipe = self.factory.makeSourcePackageRecipe()
1511+ recipe = self.makeSourcePackageRecipe()
1512 distroseries = self.factory.makeDistroSeries()
1513 (old_distroseries,) = recipe.distroseries
1514 recipe.distroseries.add(distroseries)
1515@@ -486,7 +560,7 @@
1516
1517 def test_build_daily(self):
1518 """Test that build_daily behaves as a bool."""
1519- recipe = self.factory.makeSourcePackageRecipe()
1520+ recipe = self.makeSourcePackageRecipe()
1521 self.assertFalse(recipe.build_daily)
1522 login_person(recipe.owner)
1523 recipe.build_daily = True
1524@@ -495,9 +569,9 @@
1525 def test_view_public(self):
1526 """Anyone can view a recipe with public branches."""
1527 owner = self.factory.makePerson()
1528- branch = self.factory.makeAnyBranch(owner=owner)
1529+ branch = self.makeBranch(owner=owner)
1530 with person_logged_in(owner):
1531- recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
1532+ recipe = self.makeSourcePackageRecipe(branches=[branch])
1533 self.assertTrue(check_permission('launchpad.View', recipe))
1534 with person_logged_in(self.factory.makePerson()):
1535 self.assertTrue(check_permission('launchpad.View', recipe))
1536@@ -506,19 +580,18 @@
1537 def test_view_private(self):
1538 """Recipes with private branches are restricted."""
1539 owner = self.factory.makePerson()
1540- branch = self.factory.makeAnyBranch(owner=owner)
1541+ branch = self.makeBranch(owner=owner)
1542 with person_logged_in(owner):
1543- recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
1544+ recipe = self.makeSourcePackageRecipe(branches=[branch])
1545 self.assertTrue(check_permission('launchpad.View', recipe))
1546- removeSecurityProxy(branch).information_type = (
1547- InformationType.USERDATA)
1548+ self.setInformationType(branch, InformationType.USERDATA)
1549 with person_logged_in(self.factory.makePerson()):
1550 self.assertFalse(check_permission('launchpad.View', recipe))
1551 self.assertFalse(check_permission('launchpad.View', recipe))
1552
1553 def test_edit(self):
1554 """Only the owner can edit a sourcepackagerecipe."""
1555- recipe = self.factory.makeSourcePackageRecipe()
1556+ recipe = self.makeSourcePackageRecipe()
1557 self.assertFalse(check_permission('launchpad.Edit', recipe))
1558 with person_logged_in(self.factory.makePerson()):
1559 self.assertFalse(check_permission('launchpad.Edit', recipe))
1560@@ -528,8 +601,8 @@
1561 def test_destroySelf(self):
1562 """Should destroy associated builds, distroseries, etc."""
1563 # Recipe should have at least one datainstruction.
1564- branches = [self.factory.makeBranch() for count in range(2)]
1565- recipe = self.factory.makeSourcePackageRecipe(branches=branches)
1566+ branches = [self.makeBranch() for count in range(2)]
1567+ recipe = self.makeSourcePackageRecipe(branches=branches)
1568 pending_build = self.factory.makeSourcePackageRecipeBuild(
1569 recipe=recipe)
1570 pending_build.queueBuild()
1571@@ -545,7 +618,7 @@
1572 def test_destroySelf_preserves_release(self):
1573 # Destroying a sourcepackagerecipe removes references to its builds
1574 # from their releases.
1575- recipe = self.factory.makeSourcePackageRecipe()
1576+ recipe = self.makeSourcePackageRecipe()
1577 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
1578 release = self.factory.makeSourcePackageRelease(
1579 source_package_recipe_build=build)
1580@@ -557,7 +630,7 @@
1581 def test_destroySelf_retains_build(self):
1582 # Destroying a sourcepackagerecipe removes references to its builds
1583 # from their releases.
1584- recipe = self.factory.makeSourcePackageRecipe()
1585+ recipe = self.makeSourcePackageRecipe()
1586 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
1587 store = Store.of(build)
1588 store.flush()
1589@@ -578,12 +651,11 @@
1590
1591 def test_findStaleDailyBuilds(self):
1592 # Stale recipe not built daily.
1593- self.factory.makeSourcePackageRecipe()
1594+ self.makeSourcePackageRecipe()
1595 # Daily build recipe not stale.
1596- self.factory.makeSourcePackageRecipe(
1597- build_daily=True, is_stale=False)
1598+ self.makeSourcePackageRecipe(build_daily=True, is_stale=False)
1599 # Stale daily build.
1600- stale_daily = self.factory.makeSourcePackageRecipe(
1601+ stale_daily = self.makeSourcePackageRecipe(
1602 build_daily=True, is_stale=True)
1603 self.assertContentEqual([stale_daily],
1604 SourcePackageRecipe.findStaleDailyBuilds())
1605@@ -591,8 +663,7 @@
1606 def test_findStaleDailyBuildsDistinct(self):
1607 # If a recipe has 2 builds due to 2 distroseries, it only returns
1608 # one recipe.
1609- recipe = self.factory.makeSourcePackageRecipe(
1610- build_daily=True, is_stale=True)
1611+ recipe = self.makeSourcePackageRecipe(build_daily=True, is_stale=True)
1612 hoary = self.factory.makeSourcePackageRecipeDistroseries("hoary")
1613 recipe.distroseries.add(hoary)
1614 for series in recipe.distroseries:
1615@@ -613,7 +684,7 @@
1616 build.updateStatus(
1617 BuildStatus.FULLYBUILT,
1618 date_finished=build.date_started + duration)
1619- recipe = removeSecurityProxy(self.factory.makeSourcePackageRecipe())
1620+ recipe = removeSecurityProxy(self.makeSourcePackageRecipe())
1621 self.assertIs(None, recipe.getMedianBuildDuration())
1622 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
1623 set_duration(build, 10)
1624@@ -632,7 +703,7 @@
1625
1626 def test_getBuilds(self):
1627 # Test the various getBuilds methods.
1628- recipe = self.factory.makeSourcePackageRecipe()
1629+ recipe = self.makeSourcePackageRecipe()
1630 builds = [
1631 self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
1632 for x in range(3)]
1633@@ -654,7 +725,7 @@
1634 person = self.factory.makePerson()
1635 archives = [self.factory.makeArchive(owner=person) for x in range(4)]
1636 distroseries = self.factory.makeSourcePackageRecipeDistroseries()
1637- recipe = self.factory.makeSourcePackageRecipe()
1638+ recipe = self.makeSourcePackageRecipe()
1639
1640 build_info = []
1641 for archive in archives:
1642@@ -666,7 +737,7 @@
1643
1644 def test_getBuilds_cancelled(self):
1645 # Cancelled builds are not considered pending.
1646- recipe = self.factory.makeSourcePackageRecipe()
1647+ recipe = self.makeSourcePackageRecipe()
1648 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
1649 with admin_logged_in():
1650 build.queueBuild()
1651@@ -676,40 +747,42 @@
1652 self.assertEqual([], list(recipe.pending_builds))
1653
1654 def test_setRecipeText_private_base_branch(self):
1655- source_package_recipe = self.factory.makeSourcePackageRecipe()
1656+ source_package_recipe = self.makeSourcePackageRecipe()
1657 with person_logged_in(source_package_recipe.owner):
1658- branch = self.factory.makeAnyBranch(
1659+ branch = self.makeBranch(
1660 owner=source_package_recipe.owner,
1661 information_type=InformationType.USERDATA)
1662 recipe_text = self.factory.makeRecipeText(branch)
1663 e = self.assertRaises(
1664- PrivateBranchRecipe, source_package_recipe.setRecipeText,
1665+ self.private_error, source_package_recipe.setRecipeText,
1666 recipe_text)
1667 self.assertEqual(
1668- 'Recipe may not refer to private branch: %s' %
1669- branch.bzr_identity, str(e))
1670+ 'Recipe may not refer to private %s: %s' %
1671+ (self.branch_type, self.getRepository(branch).identity),
1672+ str(e))
1673
1674 def test_setRecipeText_private_referenced_branch(self):
1675- source_package_recipe = self.factory.makeSourcePackageRecipe()
1676+ source_package_recipe = self.makeSourcePackageRecipe()
1677 with person_logged_in(source_package_recipe.owner):
1678- base_branch = self.factory.makeAnyBranch(
1679- owner=source_package_recipe.owner)
1680- referenced_branch = self.factory.makeAnyBranch(
1681+ base_branch = self.makeBranch(owner=source_package_recipe.owner)
1682+ referenced_branch = self.makeBranch(
1683 owner=source_package_recipe.owner,
1684 information_type=InformationType.USERDATA)
1685 recipe_text = self.factory.makeRecipeText(
1686 base_branch, referenced_branch)
1687 e = self.assertRaises(
1688- PrivateBranchRecipe, source_package_recipe.setRecipeText,
1689+ self.private_error, source_package_recipe.setRecipeText,
1690 recipe_text)
1691 self.assertEqual(
1692- 'Recipe may not refer to private branch: %s' %
1693- referenced_branch.bzr_identity, str(e))
1694+ 'Recipe may not refer to private %s: %s' %
1695+ (self.branch_type,
1696+ self.getRepository(referenced_branch).identity),
1697+ str(e))
1698
1699 def test_getBuilds_ignores_disabled_archive(self):
1700 # Builds into a disabled archive aren't returned.
1701 archive = self.factory.makeArchive()
1702- recipe = self.factory.makeSourcePackageRecipe()
1703+ recipe = self.makeSourcePackageRecipe()
1704 self.factory.makeSourcePackageRecipeBuild(
1705 recipe=recipe, archive=archive)
1706 with person_logged_in(archive.owner):
1707@@ -719,19 +792,19 @@
1708 self.assertEqual([], list(recipe.pending_builds))
1709
1710 def test_containsUnbuildableSeries(self):
1711- recipe = self.factory.makeSourcePackageRecipe()
1712+ recipe = self.makeSourcePackageRecipe()
1713 self.assertFalse(recipe.containsUnbuildableSeries(
1714 recipe.daily_build_archive))
1715
1716 def test_containsUnbuildableSeries_with_obsolete_series(self):
1717- recipe = self.factory.makeSourcePackageRecipe()
1718+ recipe = self.makeSourcePackageRecipe()
1719 warty = self.factory.makeSourcePackageRecipeDistroseries()
1720 removeSecurityProxy(warty).status = SeriesStatus.OBSOLETE
1721 self.assertTrue(recipe.containsUnbuildableSeries(
1722 recipe.daily_build_archive))
1723
1724 def test_performDailyBuild_filters_obsolete_series(self):
1725- recipe = self.factory.makeSourcePackageRecipe()
1726+ recipe = self.makeSourcePackageRecipe()
1727 warty = self.factory.makeSourcePackageRecipeDistroseries()
1728 hoary = self.factory.makeSourcePackageRecipeDistroseries(name='hoary')
1729 with person_logged_in(recipe.owner):
1730@@ -741,19 +814,30 @@
1731 self.assertEqual([build.recipe for build in builds], [recipe])
1732
1733
1734-class TestRecipeBranchRoundTripping(TestCaseWithFactory):
1735+class TestSourcePackageRecipeBzr(
1736+ TestSourcePackageRecipeMixin, BzrMixin, TestCaseWithFactory):
1737+ """Test `SourcePackageRecipe` objects for Bazaar."""
1738+
1739+
1740+class TestSourcePackageRecipeGit(
1741+ TestSourcePackageRecipeMixin, GitMixin, TestCaseWithFactory):
1742+ """Test `SourcePackageRecipe` objects for Git."""
1743+
1744+
1745+class TestRecipeBranchRoundTrippingMixin:
1746
1747 layer = DatabaseFunctionalLayer
1748
1749 def setUp(self):
1750- super(TestRecipeBranchRoundTripping, self).setUp()
1751- self.base_branch = self.factory.makeAnyBranch()
1752- self.nested_branch = self.factory.makeAnyBranch()
1753- self.merged_branch = self.factory.makeAnyBranch()
1754+ super(TestRecipeBranchRoundTrippingMixin, self).setUp()
1755+ self.base_branch = self.makeBranch()
1756+ self.nested_branch = self.makeBranch()
1757+ self.merged_branch = self.makeBranch()
1758 self.branch_identities = {
1759- 'base': self.base_branch.bzr_identity,
1760- 'nested': self.nested_branch.bzr_identity,
1761- 'merged': self.merged_branch.bzr_identity,
1762+ 'recipe_id': self.recipe_id,
1763+ 'base': self.getRepository(self.base_branch).identity,
1764+ 'nested': self.getRepository(self.nested_branch).identity,
1765+ 'merged': self.getRepository(self.merged_branch).identity,
1766 }
1767
1768 def get_recipe(self, recipe_text):
1769@@ -785,161 +869,173 @@
1770
1771 def test_builds_simplest_recipe(self):
1772 recipe_text = '''\
1773- # bzr-builder format 0.3 deb-version 0.1-{revno}
1774+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1775 %(base)s
1776 ''' % self.branch_identities
1777 base_branch = self.get_recipe(recipe_text).builder_recipe
1778 self.check_base_recipe_branch(
1779- base_branch, self.base_branch.bzr_identity,
1780+ base_branch, self.getRepository(self.base_branch).identity,
1781 deb_version='0.1-{revno}')
1782
1783 def test_builds_recipe_with_merge(self):
1784 recipe_text = '''\
1785- # bzr-builder format 0.3 deb-version 0.1-{revno}
1786+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1787 %(base)s
1788 merge bar %(merged)s
1789 ''' % self.branch_identities
1790 base_branch = self.get_recipe(recipe_text).builder_recipe
1791 self.check_base_recipe_branch(
1792- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
1793- deb_version='0.1-{revno}')
1794+ base_branch, self.getRepository(self.base_branch).identity,
1795+ num_child_branches=1, deb_version='0.1-{revno}')
1796 child_branch, location = base_branch.child_branches[0].as_tuple()
1797 self.assertEqual(None, location)
1798 self.check_recipe_branch(
1799- child_branch, "bar", self.merged_branch.bzr_identity)
1800+ child_branch, "bar",
1801+ self.getRepository(self.merged_branch).identity)
1802
1803 def test_builds_recipe_with_nest(self):
1804 recipe_text = '''\
1805- # bzr-builder format 0.3 deb-version 0.1-{revno}
1806+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1807 %(base)s
1808 nest bar %(nested)s baz
1809 ''' % self.branch_identities
1810 base_branch = self.get_recipe(recipe_text).builder_recipe
1811 self.check_base_recipe_branch(
1812- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
1813- deb_version='0.1-{revno}')
1814+ base_branch, self.getRepository(self.base_branch).identity,
1815+ num_child_branches=1, deb_version='0.1-{revno}')
1816 child_branch, location = base_branch.child_branches[0].as_tuple()
1817 self.assertEqual("baz", location)
1818 self.check_recipe_branch(
1819- child_branch, "bar", self.nested_branch.bzr_identity)
1820+ child_branch, "bar",
1821+ self.getRepository(self.nested_branch).identity)
1822
1823 def test_builds_recipe_with_nest_then_merge(self):
1824 recipe_text = '''\
1825- # bzr-builder format 0.3 deb-version 0.1-{revno}
1826+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1827 %(base)s
1828 nest bar %(nested)s baz
1829 merge zam %(merged)s
1830 ''' % self.branch_identities
1831 base_branch = self.get_recipe(recipe_text).builder_recipe
1832 self.check_base_recipe_branch(
1833- base_branch, self.base_branch.bzr_identity, num_child_branches=2,
1834- deb_version='0.1-{revno}')
1835+ base_branch, self.getRepository(self.base_branch).identity,
1836+ num_child_branches=2, deb_version='0.1-{revno}')
1837 child_branch, location = base_branch.child_branches[0].as_tuple()
1838 self.assertEqual("baz", location)
1839 self.check_recipe_branch(
1840- child_branch, "bar", self.nested_branch.bzr_identity)
1841+ child_branch, "bar",
1842+ self.getRepository(self.nested_branch).identity)
1843 child_branch, location = base_branch.child_branches[1].as_tuple()
1844 self.assertEqual(None, location)
1845 self.check_recipe_branch(
1846- child_branch, "zam", self.merged_branch.bzr_identity)
1847+ child_branch, "zam",
1848+ self.getRepository(self.merged_branch).identity)
1849
1850 def test_builds_recipe_with_merge_then_nest(self):
1851 recipe_text = '''\
1852- # bzr-builder format 0.3 deb-version 0.1-{revno}
1853+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1854 %(base)s
1855 merge zam %(merged)s
1856 nest bar %(nested)s baz
1857 ''' % self.branch_identities
1858 base_branch = self.get_recipe(recipe_text).builder_recipe
1859 self.check_base_recipe_branch(
1860- base_branch, self.base_branch.bzr_identity, num_child_branches=2,
1861- deb_version='0.1-{revno}')
1862+ base_branch, self.getRepository(self.base_branch).identity,
1863+ num_child_branches=2, deb_version='0.1-{revno}')
1864 child_branch, location = base_branch.child_branches[0].as_tuple()
1865 self.assertEqual(None, location)
1866 self.check_recipe_branch(
1867- child_branch, "zam", self.merged_branch.bzr_identity)
1868+ child_branch, "zam",
1869+ self.getRepository(self.merged_branch).identity)
1870 child_branch, location = base_branch.child_branches[1].as_tuple()
1871 self.assertEqual("baz", location)
1872 self.check_recipe_branch(
1873- child_branch, "bar", self.nested_branch.bzr_identity)
1874+ child_branch, "bar",
1875+ self.getRepository(self.nested_branch).identity)
1876
1877 def test_builds_a_merge_in_to_a_nest(self):
1878 recipe_text = '''\
1879- # bzr-builder format 0.3 deb-version 0.1-{revno}
1880+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1881 %(base)s
1882 nest bar %(nested)s baz
1883 merge zam %(merged)s
1884 ''' % self.branch_identities
1885 base_branch = self.get_recipe(recipe_text).builder_recipe
1886 self.check_base_recipe_branch(
1887- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
1888- deb_version='0.1-{revno}')
1889+ base_branch, self.getRepository(self.base_branch).identity,
1890+ num_child_branches=1, deb_version='0.1-{revno}')
1891 child_branch, location = base_branch.child_branches[0].as_tuple()
1892 self.assertEqual("baz", location)
1893 self.check_recipe_branch(
1894- child_branch, "bar", self.nested_branch.bzr_identity,
1895+ child_branch, "bar",
1896+ self.getRepository(self.nested_branch).identity,
1897 num_child_branches=1)
1898 child_branch, location = child_branch.child_branches[0].as_tuple()
1899 self.assertEqual(None, location)
1900 self.check_recipe_branch(
1901- child_branch, "zam", self.merged_branch.bzr_identity)
1902+ child_branch, "zam",
1903+ self.getRepository(self.merged_branch).identity)
1904
1905 def tests_builds_nest_into_a_nest(self):
1906- nested2 = self.factory.makeAnyBranch()
1907- self.branch_identities['nested2'] = nested2.bzr_identity
1908+ nested2 = self.makeBranch()
1909+ self.branch_identities['nested2'] = (
1910+ self.getRepository(nested2).identity)
1911 recipe_text = '''\
1912- # bzr-builder format 0.3 deb-version 0.1-{revno}
1913+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1914 %(base)s
1915 nest bar %(nested)s baz
1916 nest zam %(nested2)s zoo
1917 ''' % self.branch_identities
1918 base_branch = self.get_recipe(recipe_text).builder_recipe
1919 self.check_base_recipe_branch(
1920- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
1921- deb_version='0.1-{revno}')
1922+ base_branch, self.getRepository(self.base_branch).identity,
1923+ num_child_branches=1, deb_version='0.1-{revno}')
1924 child_branch, location = base_branch.child_branches[0].as_tuple()
1925 self.assertEqual("baz", location)
1926 self.check_recipe_branch(
1927- child_branch, "bar", self.nested_branch.bzr_identity,
1928+ child_branch, "bar",
1929+ self.getRepository(self.nested_branch).identity,
1930 num_child_branches=1)
1931 child_branch, location = child_branch.child_branches[0].as_tuple()
1932 self.assertEqual("zoo", location)
1933- self.check_recipe_branch(child_branch, "zam", nested2.bzr_identity)
1934+ self.check_recipe_branch(
1935+ child_branch, "zam", self.getRepository(nested2).identity)
1936
1937 def tests_builds_recipe_with_revspecs(self):
1938 recipe_text = '''\
1939- # bzr-builder format 0.3 deb-version 0.1-{revno}
1940+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1941 %(base)s revid:a
1942 nest bar %(nested)s baz tag:b
1943 merge zam %(merged)s 2
1944 ''' % self.branch_identities
1945 base_branch = self.get_recipe(recipe_text).builder_recipe
1946 self.check_base_recipe_branch(
1947- base_branch, self.base_branch.bzr_identity, num_child_branches=2,
1948- revspec="revid:a", deb_version='0.1-{revno}')
1949+ base_branch, self.getRepository(self.base_branch).identity,
1950+ num_child_branches=2, revspec="revid:a", deb_version='0.1-{revno}')
1951 instruction = base_branch.child_branches[0]
1952 child_branch = instruction.recipe_branch
1953 location = instruction.nest_path
1954 self.assertEqual("baz", location)
1955 self.check_recipe_branch(
1956- child_branch, "bar", self.nested_branch.bzr_identity,
1957- revspec="tag:b")
1958+ child_branch, "bar",
1959+ self.getRepository(self.nested_branch).identity, revspec="tag:b")
1960 child_branch, location = base_branch.child_branches[1].as_tuple()
1961 self.assertEqual(None, location)
1962 self.check_recipe_branch(
1963- child_branch, "zam", self.merged_branch.bzr_identity, revspec="2")
1964+ child_branch, "zam",
1965+ self.getRepository(self.merged_branch).identity, revspec="2")
1966
1967 def test_unsets_revspecs(self):
1968 # Changing a recipe's text to no longer include revspecs unsets
1969 # them from the stored copy.
1970 revspec_text = '''\
1971- # bzr-builder format 0.3 deb-version 0.1-{revno}
1972+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1973 %(base)s revid:a
1974 nest bar %(nested)s baz tag:b
1975 merge zam %(merged)s 2
1976 ''' % self.branch_identities
1977 no_revspec_text = '''\
1978- # bzr-builder format 0.3 deb-version 0.1-{revno}
1979+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1980 %(base)s
1981 nest bar %(nested)s baz
1982 merge zam %(merged)s
1983@@ -952,18 +1048,29 @@
1984
1985 def test_builds_recipe_without_debversion(self):
1986 recipe_text = '''\
1987- # bzr-builder format 0.4
1988+ # %(recipe_id)s format 0.4
1989 %(base)s
1990 nest bar %(nested)s baz
1991 ''' % self.branch_identities
1992 base_branch = self.get_recipe(recipe_text).builder_recipe
1993 self.check_base_recipe_branch(
1994- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
1995- deb_version=None)
1996+ base_branch, self.getRepository(self.base_branch).identity,
1997+ num_child_branches=1, deb_version=None)
1998 child_branch, location = base_branch.child_branches[0].as_tuple()
1999 self.assertEqual("baz", location)
2000 self.check_recipe_branch(
2001- child_branch, "bar", self.nested_branch.bzr_identity)
2002+ child_branch, "bar",
2003+ self.getRepository(self.nested_branch).identity)
2004+
2005+
2006+class TestRecipeBranchRoundTrippingBzr(
2007+ TestRecipeBranchRoundTrippingMixin, BzrMixin, TestCaseWithFactory):
2008+ pass
2009+
2010+
2011+class TestRecipeBranchRoundTrippingGit(
2012+ TestRecipeBranchRoundTrippingMixin, GitMixin, TestCaseWithFactory):
2013+ pass
2014
2015
2016 class RecipeDateLastModified(TestCaseWithFactory):
2017@@ -991,14 +1098,10 @@
2018 self.recipe, 'date_last_modified', UTC_NOW)
2019
2020
2021-class TestWebservice(TestCaseWithFactory):
2022+class TestWebserviceMixin:
2023
2024 layer = AppServerLayer
2025
2026- def makeRecipeText(self):
2027- branch = self.factory.makeBranch()
2028- return MINIMAL_RECIPE_TEXT % branch.bzr_identity
2029-
2030 def makeRecipe(self, user=None, owner=None, recipe_text=None,
2031 version='devel'):
2032 # rockstar 21 Jul 2010 - This function does more commits than I'd
2033@@ -1172,3 +1275,11 @@
2034 with StormStatementRecorder() as recorder:
2035 webservice.get(url)
2036 self.assertThat(recorder, HasQueryCount(Equals(23)))
2037+
2038+
2039+class TestWebserviceBzr(TestWebserviceMixin, BzrMixin, TestCaseWithFactory):
2040+ pass
2041+
2042+
2043+class TestWebserviceGit(TestWebserviceMixin, GitMixin, TestCaseWithFactory):
2044+ pass
2045
2046=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
2047--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2015-10-19 10:56:16 +0000
2048+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2016-01-12 04:06:05 +0000
2049@@ -1,4 +1,4 @@
2050-# Copyright 2010-2013 Canonical Ltd. This software is licensed under the
2051+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
2052 # GNU Affero General Public License version 3 (see the file LICENSE).
2053
2054 """Tests for source package builds."""
2055@@ -106,11 +106,11 @@
2056 self.assertEqual(bq, spb.buildqueue_record)
2057
2058 def test_title(self):
2059- # A recipe build's title currently consists of the base
2060- # branch's unique name.
2061+ # A recipe build's title currently consists of the base source
2062+ # location's unique name.
2063 spb = self.makeSourcePackageRecipeBuild()
2064 title = "%s recipe build in %s %s" % (
2065- spb.recipe.base_branch.unique_name, spb.distribution.name,
2066+ spb.recipe.base.unique_name, spb.distribution.name,
2067 spb.distroseries.name)
2068 self.assertEqual(spb.title, title)
2069
2070
2071=== modified file 'lib/lp/registry/model/product.py'
2072--- lib/lp/registry/model/product.py 2015-10-01 17:32:41 +0000
2073+++ lib/lp/registry/model/product.py 2016-01-12 04:06:05 +0000
2074@@ -1,4 +1,4 @@
2075-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
2076+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
2077 # GNU Affero General Public License version 3 (see the file LICENSE).
2078
2079 """Database classes including and related to Product."""
2080@@ -42,6 +42,7 @@
2081 Or,
2082 Select,
2083 SQL,
2084+ Union,
2085 )
2086 from storm.locals import (
2087 Store,
2088@@ -122,6 +123,7 @@
2089 from lp.code.interfaces.gitrepository import IGitRepositorySet
2090 from lp.code.model.branch import Branch
2091 from lp.code.model.branchnamespace import BRANCH_POLICY_ALLOWED_TYPES
2092+from lp.code.model.gitrepository import GitRepository
2093 from lp.code.model.hasbranches import (
2094 HasBranchesMixin,
2095 HasCodeImportsMixin,
2096@@ -1578,8 +1580,18 @@
2097 SourcePackageRecipe,
2098 SourcePackageRecipe.id ==
2099 SourcePackageRecipeData.sourcepackage_recipe_id,
2100- SourcePackageRecipeData.base_branch == Branch.id,
2101- Branch.product == self)
2102+ SourcePackageRecipeData.id.is_in(Union(
2103+ Select(
2104+ SourcePackageRecipeData.id,
2105+ And(
2106+ SourcePackageRecipeData.base_branch == Branch.id,
2107+ Branch.product == self)),
2108+ Select(
2109+ SourcePackageRecipeData.id,
2110+ And(
2111+ SourcePackageRecipeData.base_git_repository ==
2112+ GitRepository.id,
2113+ GitRepository.project == self)))))
2114 hook = SourcePackageRecipe.preLoadDataForSourcePackageRecipes
2115 return DecoratedResultSet(recipes, pre_iter_hook=hook)
2116
2117
2118=== modified file 'lib/lp/testing/factory.py'
2119--- lib/lp/testing/factory.py 2015-10-13 13:22:08 +0000
2120+++ lib/lp/testing/factory.py 2016-01-12 04:06:05 +0000
2121@@ -2,7 +2,7 @@
2122 # NOTE: The first line above must stay first; do not move the copyright
2123 # notice to the top. See http://www.python.org/dev/peps/pep-0263/.
2124 #
2125-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
2126+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
2127 # GNU Affero General Public License version 3 (see the file LICENSE).
2128
2129 """Testing infrastructure for the Launchpad application.
2130@@ -112,6 +112,7 @@
2131 RevisionControlSystems,
2132 )
2133 from lp.code.errors import UnknownBranchTypeError
2134+from lp.code.interfaces.branch import IBranch
2135 from lp.code.interfaces.branchnamespace import get_branch_namespace
2136 from lp.code.interfaces.branchtarget import IBranchTarget
2137 from lp.code.interfaces.codeimport import ICodeImportSet
2138@@ -119,11 +120,13 @@
2139 from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet
2140 from lp.code.interfaces.codeimportresult import ICodeImportResultSet
2141 from lp.code.interfaces.gitnamespace import get_git_namespace
2142+from lp.code.interfaces.gitref import IGitRef
2143 from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
2144 from lp.code.interfaces.revision import IRevisionSet
2145 from lp.code.interfaces.sourcepackagerecipe import (
2146 ISourcePackageRecipeSource,
2147- MINIMAL_RECIPE_TEXT,
2148+ MINIMAL_RECIPE_TEXT_BZR,
2149+ MINIMAL_RECIPE_TEXT_GIT,
2150 )
2151 from lp.code.interfaces.sourcepackagerecipebuild import (
2152 ISourcePackageRecipeBuildSource,
2153@@ -2897,9 +2900,22 @@
2154 branches = (self.makeAnyBranch(), )
2155 base_branch = branches[0]
2156 other_branches = branches[1:]
2157- text = MINIMAL_RECIPE_TEXT % base_branch.bzr_identity
2158+ if IBranch.providedBy(base_branch):
2159+ text = MINIMAL_RECIPE_TEXT_BZR % base_branch.identity
2160+ elif IGitRef.providedBy(base_branch):
2161+ text = MINIMAL_RECIPE_TEXT_GIT % (
2162+ base_branch.repository.identity, base_branch.name)
2163+ else:
2164+ raise AssertionError(
2165+ "Unsupported base_branch: %r" % (base_branch,))
2166 for i, branch in enumerate(other_branches):
2167- text += 'merge dummy-%s %s\n' % (i, branch.bzr_identity)
2168+ if IBranch.providedBy(branch):
2169+ text += 'merge dummy-%s %s\n' % (i, branch.identity)
2170+ elif IGitRef.providedBy(branch):
2171+ text += 'merge dummy-%s %s %s\n' % (
2172+ i, branch.repository.identity, branch.name)
2173+ else:
2174+ raise AssertionError("Unsupported branch: %r" % (branch,))
2175 return text
2176
2177 def makeRecipe(self, *branches):