launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #19854
[Merge] lp:~cjwatson/launchpad/git-recipe-find into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/git-recipe-find into lp:launchpad.
Commit message:
Add IHasRecipes implementations and other methods for finding Git recipes.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1453022 in Launchpad itself: "Please support daily builds via git"
https://bugs.launchpad.net/launchpad/+bug/1453022
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/git-recipe-find/+merge/282252
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.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/git-recipe-find into lp:launchpad.
=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
--- lib/lp/code/browser/sourcepackagerecipe.py 2015-07-08 16:05:11 +0000
+++ lib/lp/code/browser/sourcepackagerecipe.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2013 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""SourcePackageRecipe views."""
@@ -20,7 +20,6 @@
from bzrlib.plugins.builder.recipe import (
ForbiddenInstructionError,
RecipeParseError,
- RecipeParser,
)
from lazr.lifecycle.event import ObjectModifiedEvent
from lazr.lifecycle.snapshot import Snapshot
@@ -92,9 +91,10 @@
)
from lp.code.interfaces.branchtarget import IBranchTarget
from lp.code.interfaces.sourcepackagerecipe import (
+ IRecipeBranchSource,
ISourcePackageRecipe,
ISourcePackageRecipeSource,
- MINIMAL_RECIPE_TEXT,
+ MINIMAL_RECIPE_TEXT_BZR,
)
from lp.code.model.branchtarget import PersonBranchTarget
from lp.code.vocabularies.sourcepackagerecipe import BuildableDistroSeries
@@ -611,10 +611,11 @@
'distroseries',
'You must specify at least one series for daily builds.')
try:
- parser = RecipeParser(data['recipe_text'])
- parser.parse()
- except RecipeParseError as error:
- self.setFieldError('recipe_text', str(error))
+ self.error_handler(
+ getUtility(IRecipeBranchSource).getParsedRecipe,
+ data['recipe_text'])
+ except ErrorHandled:
+ pass
def error_handler(self, callable, *args, **kwargs):
try:
@@ -631,7 +632,7 @@
except NoSuchBranch as e:
self.setFieldError(
'recipe_text', '%s is not a branch on Launchpad.' % e.name)
- except PrivateBranchRecipe as e:
+ except (RecipeParseError, PrivateBranchRecipe) as e:
self.setFieldError('recipe_text', str(e))
raise ErrorHandled()
@@ -760,7 +761,7 @@
SeriesStatus.CURRENT, SeriesStatus.DEVELOPMENT)]
return {
'name': self._find_unused_name(self.user),
- 'recipe_text': MINIMAL_RECIPE_TEXT % self.context.bzr_identity,
+ 'recipe_text': MINIMAL_RECIPE_TEXT_BZR % self.context.bzr_identity,
'owner': self.user,
'distroseries': series,
'build_daily': True,
@@ -872,14 +873,14 @@
self.context, providing=providedBy(self.context))
recipe_text = data.pop('recipe_text')
- parser = RecipeParser(recipe_text)
- recipe = parser.parse()
- if self.context.builder_recipe != recipe:
- try:
+ try:
+ recipe = self.error_handler(
+ getUtility(IRecipeBranchSource).getParsedRecipe, recipe_text)
+ if self.context.builder_recipe != recipe:
self.error_handler(self.context.setRecipeText, recipe_text)
changed = True
- except ErrorHandled:
- return
+ except ErrorHandled:
+ return
distros = data.pop('distroseries')
if distros != self.context.distroseries:
@@ -927,7 +928,7 @@
label = title
class schema(Interface):
- """Schema for deleting a branch."""
+ """Schema for deleting a recipe."""
@property
def cancel_url(self):
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2015-09-28 17:38:45 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2016-01-12 04:06:05 +0000
@@ -34,7 +34,7 @@
from lp.code.browser.sourcepackagerecipebuild import (
SourcePackageRecipeBuildView,
)
-from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT
+from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT_BZR
from lp.code.tests.helpers import recipe_parser_newest_version
from lp.registry.interfaces.person import TeamMembershipPolicy
from lp.registry.interfaces.pocket import PackagePublishingPocket
@@ -449,7 +449,7 @@
def test_create_recipe_bad_base_branch(self):
# If a user tries to create source package recipe with a bad base
# branch location, they should get an error.
- browser = self.createRecipe(MINIMAL_RECIPE_TEXT % 'foo')
+ browser = self.createRecipe(MINIMAL_RECIPE_TEXT_BZR % 'foo')
self.assertEqual(
get_feedback_messages(browser.contents)[1],
'foo is not a branch on Launchpad.')
@@ -461,7 +461,7 @@
name='ratatouille', displayname='Ratatouille')
branch = self.factory.makeBranch(
owner=self.chef, product=product, name='veggies')
- recipe = MINIMAL_RECIPE_TEXT % branch.bzr_identity
+ recipe = MINIMAL_RECIPE_TEXT_BZR % branch.bzr_identity
recipe += 'nest packaging foo debian'
browser = self.createRecipe(recipe, branch)
self.assertEqual(
@@ -518,7 +518,7 @@
owner=self.user, information_type=InformationType.USERDATA)
with person_logged_in(self.user):
bzr_identity = branch.bzr_identity
- recipe_text = MINIMAL_RECIPE_TEXT % bzr_identity
+ recipe_text = MINIMAL_RECIPE_TEXT_BZR % bzr_identity
browser = self.createRecipe(recipe_text)
self.assertEqual(
get_feedback_messages(browser.contents)[1],
@@ -761,7 +761,7 @@
browser.getControl(name='field.name').value = 'fings'
browser.getControl('Description').value = 'This is stuff'
browser.getControl('Recipe text').value = (
- MINIMAL_RECIPE_TEXT % meat_path)
+ MINIMAL_RECIPE_TEXT_BZR % meat_path)
browser.getControl('Secret Squirrel').click()
browser.getControl('Mumbly Midget').click()
browser.getControl('PPA 2').click()
@@ -828,7 +828,7 @@
browser.getControl(name='field.name').value = 'fings'
browser.getControl('Description').value = 'This is stuff'
browser.getControl('Recipe text').value = (
- MINIMAL_RECIPE_TEXT % meat_path)
+ MINIMAL_RECIPE_TEXT_BZR % meat_path)
browser.getControl('Secret Squirrel').click()
browser.getControl('Mumbly Midget').click()
browser.getControl('Update Recipe').click()
@@ -926,7 +926,7 @@
browser.getControl(name='field.name').value = 'fings'
browser.getControl('Description').value = 'This is stuff'
browser.getControl('Recipe text').value = (
- MINIMAL_RECIPE_TEXT % meat_path)
+ MINIMAL_RECIPE_TEXT_BZR % meat_path)
browser.getControl('Secret Squirrel').click()
browser.getControl('Mumbly Midget').click()
browser.getControl('Update Recipe').click()
@@ -943,7 +943,7 @@
owner=self.user, information_type=InformationType.USERDATA)
with person_logged_in(self.user):
bzr_identity = branch.bzr_identity
- recipe_text = MINIMAL_RECIPE_TEXT % bzr_identity
+ recipe_text = MINIMAL_RECIPE_TEXT_BZR % bzr_identity
browser = self.getViewBrowser(recipe, '+edit')
browser.getControl('Recipe text').value = recipe_text
browser.getControl('Update Recipe').click()
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml 2015-10-09 16:56:45 +0000
+++ lib/lp/code/configure.zcml 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2009-2015 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2009-2016 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -1053,6 +1053,15 @@
<require permission="launchpad.View"
interface="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipeData"/>
</class>
+ <utility
+ component="lp.code.model.sourcepackagerecipedata.SourcePackageRecipeData"
+ provides="lp.code.interfaces.sourcepackagerecipe.IRecipeBranchSource">
+ </utility>
+ <securedutility
+ component="lp.code.model.sourcepackagerecipedata.SourcePackageRecipeData"
+ provides="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipeDataSource">
+ <allow interface="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipeDataSource"/>
+ </securedutility>
<!-- SourcePackageRecipe -->
<class
class="lp.code.model.sourcepackagerecipe.SourcePackageRecipe">
=== modified file 'lib/lp/code/errors.py'
--- lib/lp/code/errors.py 2015-06-13 01:45:19 +0000
+++ lib/lp/code/errors.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Errors used in the lp/code modules."""
@@ -46,6 +46,7 @@
'NoSuchGitReference',
'NoSuchGitRepository',
'PrivateBranchRecipe',
+ 'PrivateGitRepositoryRecipe',
'ReviewNotPending',
'StaleLastMirrored',
'TooNewRecipeFormat',
@@ -298,12 +299,22 @@
def __init__(self, branch):
message = (
- 'Recipe may not refer to private branch: %s' %
- branch.bzr_identity)
+ 'Recipe may not refer to private branch: %s' % branch.identity)
self.branch = branch
Exception.__init__(self, message)
+@error_status(httplib.BAD_REQUEST)
+class PrivateGitRepositoryRecipe(Exception):
+
+ def __init__(self, repository):
+ message = (
+ 'Recipe may not refer to private repository: %s' %
+ repository.identity)
+ self.repository = repository
+ Exception.__init__(self, message)
+
+
class ReviewNotPending(Exception):
"""The requested review is not in a pending state."""
=== modified file 'lib/lp/code/interfaces/gitref.py'
--- lib/lp/code/interfaces/gitref.py 2015-11-23 11:34:15 +0000
+++ lib/lp/code/interfaces/gitref.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Git reference ("ref") interfaces."""
@@ -47,11 +47,12 @@
GitObjectType,
)
from lp.code.interfaces.hasbranches import IHasMergeProposals
+from lp.code.interfaces.hasrecipes import IHasRecipes
from lp.registry.interfaces.person import IPerson
from lp.services.webapp.interfaces import ITableBatchNavigator
-class IGitRef(IHasMergeProposals, IPrivacy, IInformationType):
+class IGitRef(IHasMergeProposals, IHasRecipes, IPrivacy, IInformationType):
"""A reference in a Git repository."""
# XXX cjwatson 2015-01-19 bug=760849: "beta" is a lie to get WADL
=== modified file 'lib/lp/code/interfaces/gitrepository.py'
--- lib/lp/code/interfaces/gitrepository.py 2015-11-02 15:31:39 +0000
+++ lib/lp/code/interfaces/gitrepository.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Git repository interfaces."""
@@ -64,6 +64,7 @@
)
from lp.code.interfaces.defaultgit import ICanHasDefaultGitRepository
from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
+from lp.code.interfaces.hasrecipes import IHasRecipes
from lp.registry.interfaces.distributionsourcepackage import (
IDistributionSourcePackage,
)
@@ -118,7 +119,7 @@
return True
-class IGitRepositoryView(Interface):
+class IGitRepositoryView(IHasRecipes):
"""IGitRepository attributes that require launchpad.View permission."""
id = Int(title=_("ID"), readonly=True, required=True)
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
--- lib/lp/code/interfaces/sourcepackagerecipe.py 2015-04-30 01:45:30 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Interface of the `SourcePackageRecipe` content type."""
@@ -8,10 +8,13 @@
__all__ = [
+ 'IRecipeBranchSource',
'ISourcePackageRecipe',
'ISourcePackageRecipeData',
+ 'ISourcePackageRecipeDataSource',
'ISourcePackageRecipeSource',
- 'MINIMAL_RECIPE_TEXT',
+ 'MINIMAL_RECIPE_TEXT_BZR',
+ 'MINIMAL_RECIPE_TEXT_GIT',
]
@@ -53,6 +56,7 @@
from lp import _
from lp.app.validators.name import name_validator
from lp.code.interfaces.branch import IBranch
+from lp.code.interfaces.gitrepository import IGitRepository
from lp.registry.interfaces.distroseries import IDistroSeries
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.role import IHasOwner
@@ -64,19 +68,32 @@
from lp.soyuz.interfaces.archive import IArchive
-MINIMAL_RECIPE_TEXT = dedent(u'''\
+MINIMAL_RECIPE_TEXT_BZR = dedent(u'''\
# bzr-builder format 0.3 deb-version {debupstream}-0~{revno}
%s
''')
+MINIMAL_RECIPE_TEXT_GIT = dedent(u'''\
+ # git-build-recipe format 0.4 deb-version {debupstream}-0~{revtime}
+ %s %s
+ ''')
+
+
class ISourcePackageRecipeData(Interface):
"""A recipe as database data, not text."""
base_branch = exported(
Reference(
IBranch, title=_("The base branch used by this recipe."),
- required=True, readonly=True))
+ required=False, readonly=True))
+ base_git_repository = exported(
+ Reference(
+ IGitRepository,
+ title=_("The base Git repository used by this recipe."),
+ required=False, readonly=True))
+ base = Attribute(
+ "The base branch/repository used by this recipe (VCS-agnostic).")
deb_version_template = exported(
TextLine(
@@ -88,6 +105,28 @@
"""An iterator of the branches referenced by this recipe."""
+class IRecipeBranchSource(Interface):
+
+ def getParsedRecipe(recipe_text):
+ """Parse recipe text into recipe data.
+
+ :param recipe_text: Recipe text as a string.
+ :return: a `RecipeBranch` representing the recipe.
+ """
+
+
+class ISourcePackageRecipeDataSource(Interface):
+
+ def createManifestFromText(text, sourcepackage_recipe_build):
+ """Create a manifest for the specified build.
+
+ :param text: The text of the recipe to create a manifest for.
+ :param sourcepackage_recipe_build: The build to associate the manifest
+ with.
+ :return: an instance of `SourcePackageRecipeData`.
+ """
+
+
class ISourcePackageRecipeView(Interface):
"""IBranch attributes that require launchpad.View permission."""
=== modified file 'lib/lp/code/model/gitref.py'
--- lib/lp/code/model/gitref.py 2015-11-23 11:34:15 +0000
+++ lib/lp/code/model/gitref.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -242,6 +242,18 @@
"""See `IGitRef`."""
return self.repository.pending_writes
+ @property
+ def recipes(self):
+ """See `IHasRecipes`."""
+ from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
+ from lp.code.model.sourcepackagerecipedata import (
+ SourcePackageRecipeData,
+ )
+ recipes = SourcePackageRecipeData.findRecipes(
+ self.repository, revspecs=list(set([self.path, self.name])))
+ hook = SourcePackageRecipe.preLoadDataForSourcePackageRecipes
+ return DecoratedResultSet(recipes, pre_iter_hook=hook)
+
@implementer(IGitRef)
class GitRef(StormBase, GitRefMixin):
=== modified file 'lib/lp/code/model/gitrepository.py'
--- lib/lp/code/model/gitrepository.py 2015-12-10 00:05:41 +0000
+++ lib/lp/code/model/gitrepository.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -130,6 +130,7 @@
DEFAULT,
UTC_NOW,
)
+from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.enumcol import EnumCol
from lp.services.database.interfaces import IStore
from lp.services.database.stormbase import StormBase
@@ -953,6 +954,29 @@
jobs.append(UpdatePreviewDiffJob.create(merge_proposal))
return jobs
+ def _getRecipes(self, paths=None):
+ """Undecorated version of recipes for use by `markRecipesStale`."""
+ from lp.code.model.sourcepackagerecipedata import (
+ SourcePackageRecipeData,
+ )
+ if paths is not None:
+ revspecs = set()
+ for path in paths:
+ revspecs.add(path)
+ if path.startswith("refs/heads/"):
+ revspecs.add(path[len("refs/heads/"):])
+ revspecs = list(revspecs)
+ else:
+ revspecs = None
+ return SourcePackageRecipeData.findRecipes(self, revspecs=revspecs)
+
+ @property
+ def recipes(self):
+ """See `IHasRecipes`."""
+ from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
+ hook = SourcePackageRecipe.preLoadDataForSourcePackageRecipes
+ return DecoratedResultSet(self._getRecipes(), pre_iter_hook=hook)
+
def _markProposalMerged(self, proposal, merged_revision_id, logger=None):
if logger is not None:
logger.info(
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2015-09-28 17:38:45 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Implementation of the `SourcePackageRecipe` content type."""
@@ -13,6 +13,7 @@
timedelta,
)
from operator import attrgetter
+import re
from lazr.delegates import delegate_to
from pytz import utc
@@ -42,6 +43,7 @@
BuildNotAllowedForDistro,
)
from lp.code.interfaces.sourcepackagerecipe import (
+ IRecipeBranchSource,
ISourcePackageRecipe,
ISourcePackageRecipeData,
ISourcePackageRecipeSource,
@@ -150,6 +152,18 @@
def base_branch(self):
return self._recipe_data.base_branch
+ @property
+ def base_git_repository(self):
+ return self._recipe_data.base_git_repository
+
+ @property
+ def base(self):
+ if self.base_branch is not None:
+ return self.base_branch
+ else:
+ assert self.base_git_repository is not None
+ return self.base_git_repository
+
@staticmethod
def preLoadDataForSourcePackageRecipes(sourcepackagerecipes):
# Load the referencing SourcePackageRecipeData.
@@ -167,12 +181,19 @@
owner_ids, need_validity=True))
def setRecipeText(self, recipe_text):
- parsed = SourcePackageRecipeData.getParsedRecipe(recipe_text)
+ parsed = getUtility(IRecipeBranchSource).getParsedRecipe(recipe_text)
self._recipe_data.setRecipe(parsed)
@property
def recipe_text(self):
- return self.builder_recipe.get_recipe_text()
+ recipe_text = self.builder_recipe.get_recipe_text()
+ # For git-based recipes, mangle the header line to say
+ # "git-build-recipe" to reduce confusion; bzr-builder's recipe
+ # parser will always round-trip this to "bzr-builder".
+ if self.base_git_repository is not None:
+ recipe_text = re.sub(
+ r"^(#\s*)bzr-builder", r"\1git-build-recipe", recipe_text)
+ return recipe_text
def updateSeries(self, distroseries):
if distroseries != self.distroseries:
@@ -187,7 +208,8 @@
"""See `ISourcePackageRecipeSource.new`."""
store = IMasterStore(SourcePackageRecipe)
sprecipe = SourcePackageRecipe()
- builder_recipe = SourcePackageRecipeData.getParsedRecipe(recipe)
+ builder_recipe = getUtility(IRecipeBranchSource).getParsedRecipe(
+ recipe)
SourcePackageRecipeData(builder_recipe, sprecipe)
sprecipe.registrant = registrant
sprecipe.owner = owner
=== modified file 'lib/lp/code/model/sourcepackagerecipebuild.py'
--- lib/lp/code/model/sourcepackagerecipebuild.py 2015-09-11 15:11:34 +0000
+++ lib/lp/code/model/sourcepackagerecipebuild.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2013 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Implementation code for source package builds."""
@@ -46,6 +46,10 @@
BuildAlreadyPending,
BuildNotAllowedForDistro,
)
+from lp.code.interfaces.sourcepackagerecipe import (
+ IRecipeBranchSource,
+ ISourcePackageRecipeDataSource,
+ )
from lp.code.interfaces.sourcepackagerecipebuild import (
ISourcePackageRecipeBuild,
ISourcePackageRecipeBuildSource,
@@ -53,7 +57,6 @@
from lp.code.mail.sourcepackagerecipebuild import (
SourcePackageRecipeBuildMailer,
)
-from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.model.person import Person
from lp.services.database.bulk import load_related
@@ -158,10 +161,11 @@
if self.manifest is not None:
IStore(self.manifest).remove(self.manifest)
elif self.manifest is None:
- SourcePackageRecipeData.createManifestFromText(text, self)
+ getUtility(ISourcePackageRecipeDataSource).createManifestFromText(
+ text, self)
else:
- from bzrlib.plugins.builder.recipe import RecipeParser
- self.manifest.setRecipe(RecipeParser(text).parse())
+ self.manifest.setRecipe(
+ getUtility(IRecipeBranchSource).getParsedRecipe(text))
def getManifestText(self):
if self.manifest is None:
@@ -179,7 +183,7 @@
if self.recipe is None:
branch_name = 'deleted'
else:
- branch_name = self.recipe.base_branch.unique_name
+ branch_name = self.recipe.base.unique_name
return '%s recipe build in %s %s' % (
branch_name, self.distribution.name, self.distroseries.name)
=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
--- lib/lp/code/model/sourcepackagerecipedata.py 2015-02-25 11:21:43 +0000
+++ lib/lp/code/model/sourcepackagerecipedata.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Implementation of the recipe storage.
@@ -12,6 +12,7 @@
__all__ = ['SourcePackageRecipeData']
from itertools import groupby
+import re
from bzrlib.plugins.builder.recipe import (
BaseRecipeBranch,
@@ -29,6 +30,7 @@
from storm.expr import Union
from storm.locals import (
And,
+ In,
Int,
Reference,
ReferenceSet,
@@ -38,14 +40,30 @@
Unicode,
)
from zope.component import getUtility
+from zope.interface import (
+ implementer,
+ provider,
+ )
from lp.code.errors import (
NoSuchBranch,
+ NoSuchGitRepository,
PrivateBranchRecipe,
+ PrivateGitRepositoryRecipe,
TooNewRecipeFormat,
)
+from lp.code.interfaces.branch import IBranch
from lp.code.interfaces.branchlookup import IBranchLookup
+from lp.code.interfaces.gitlookup import IGitLookup
+from lp.code.interfaces.gitref import IGitRef
+from lp.code.interfaces.gitrepository import IGitRepository
+from lp.code.interfaces.sourcepackagerecipe import (
+ IRecipeBranchSource,
+ ISourcePackageRecipeData,
+ ISourcePackageRecipeDataSource,
+ )
from lp.code.model.branch import Branch
+from lp.code.model.gitrepository import GitRepository
from lp.services.database.bulk import (
load_referencing,
load_related,
@@ -83,15 +101,25 @@
__storm_table__ = "SourcePackageRecipeDataInstruction"
- def __init__(self, name, type, comment, line_number, branch, revspec,
- directory, recipe_data, parent_instruction,
+ def __init__(self, name, type, comment, line_number, branch_or_repository,
+ revspec, directory, recipe_data, parent_instruction,
source_directory):
super(_SourcePackageRecipeDataInstruction, self).__init__()
self.name = unicode(name)
self.type = type
self.comment = comment
self.line_number = line_number
- self.branch = branch
+ if IGitRepository.providedBy(branch_or_repository):
+ self.git_repository = branch_or_repository
+ elif IGitRef.providedBy(branch_or_repository):
+ self.git_repository = branch_or_repository
+ if revspec is None:
+ revspec = branch_or_repository.name
+ elif IBranch.providedBy(branch_or_repository):
+ self.branch = branch_or_repository
+ else:
+ raise AssertionError(
+ "Unsupported source: %r" % (branch_or_repository,))
if revspec is not None:
revspec = unicode(revspec)
self.revspec = revspec
@@ -109,8 +137,10 @@
comment = Unicode(allow_none=True)
line_number = Int(allow_none=False)
- branch_id = Int(name='branch', allow_none=False)
+ branch_id = Int(name='branch', allow_none=True)
branch = Reference(branch_id, 'Branch.id')
+ git_repository_id = Int(name='git_repository', allow_none=True)
+ git_repository = Reference(git_repository_id, 'GitRepository.id')
revspec = Unicode(allow_none=True)
directory = Unicode(allow_none=True)
@@ -125,8 +155,12 @@
def appendToRecipe(self, recipe_branch):
"""Append a bzr-builder instruction to the recipe_branch object."""
- branch = RecipeBranch(
- self.name, self.branch.bzr_identity, self.revspec)
+ if self.branch is not None:
+ identity = self.branch.identity
+ else:
+ assert self.git_repository is not None
+ identity = self.git_repository.identity
+ branch = RecipeBranch(self.name, identity, self.revspec)
if self.type == InstructionType.MERGE:
recipe_branch.merge_branch(branch)
elif self.type == InstructionType.NEST:
@@ -142,6 +176,8 @@
MAX_RECIPE_FORMAT = 0.4
+@implementer(ISourcePackageRecipeData)
+@provider(IRecipeBranchSource, ISourcePackageRecipeDataSource)
class SourcePackageRecipeData(Storm):
"""The database representation of a BaseRecipeBranch from bzr-builder.
@@ -154,8 +190,29 @@
id = Int(primary=True)
- base_branch_id = Int(name='base_branch', allow_none=False)
+ base_branch_id = Int(name='base_branch', allow_none=True)
base_branch = Reference(base_branch_id, 'Branch.id')
+ base_git_repository_id = Int(name='base_git_repository', allow_none=True)
+ base_git_repository = Reference(base_git_repository_id, 'GitRepository.id')
+
+ @property
+ def base(self):
+ if self.base_branch is not None:
+ return self.base_branch
+ else:
+ assert self.base_git_repository is not None
+ return self.base_git_repository
+
+ @base.setter
+ def base(self, value):
+ if IGitRepository.providedBy(value):
+ self.base_git_repository = value
+ self.base_branch = None
+ elif IBranch.providedBy(value):
+ self.base_branch = value
+ self.base_git_repository = None
+ else:
+ raise AssertionError("Unsupported base: %r" % (value,))
recipe_format = Unicode(allow_none=False)
deb_version_template = Unicode(allow_none=True)
@@ -177,38 +234,68 @@
@staticmethod
def getParsedRecipe(recipe_text):
+ """See `IRecipeBranchSource`."""
+ # We're using bzr-builder to parse the recipe text. While the
+ # formats are mostly compatible, the header line must say
+ # "bzr-builder" even though git-build-recipe also supports its own
+ # name there.
+ recipe_text = re.sub(
+ r"^(#\s*)git-build-recipe", r"\1bzr-builder", recipe_text)
parser = RecipeParser(recipe_text)
return parser.parse(permitted_instructions=SAFE_INSTRUCTIONS)
@staticmethod
- def findRecipes(branch):
+ def findRecipes(branch_or_repository, revspecs=None):
+ """Find recipes for a given branch or repository.
+
+ :param branch_or_repository: The branch or repository to search for.
+ :param revspecs: If not None, return only recipes whose `revspec` is
+ in this sequence.
+ :return: a collection of `ISourcePackageRecipe`s.
+ """
from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
- store = Store.of(branch)
+ store = Store.of(branch_or_repository)
+ if IGitRepository.providedBy(branch_or_repository):
+ data_clause = (
+ SourcePackageRecipeData.base_git_repository ==
+ branch_or_repository)
+ insn_clause = (
+ _SourcePackageRecipeDataInstruction.git_repository ==
+ branch_or_repository)
+ elif IBranch.providedBy(branch_or_repository):
+ data_clause = (
+ SourcePackageRecipeData.base_branch == branch_or_repository)
+ insn_clause = (
+ _SourcePackageRecipeDataInstruction.branch ==
+ branch_or_repository)
+ else:
+ raise AssertionError(
+ "Unsupported source: %r" % (branch_or_repository,))
+ if revspecs is not None:
+ data_clause = And(
+ data_clause, In(SourcePackageRecipeData.revspec, revspecs))
+ insn_clause = And(
+ insn_clause,
+ In(_SourcePackageRecipeDataInstruction.revspec, revspecs))
return store.find(
SourcePackageRecipe,
SourcePackageRecipe.id.is_in(Union(
Select(
SourcePackageRecipeData.sourcepackage_recipe_id,
- SourcePackageRecipeData.base_branch == branch),
+ data_clause),
Select(
SourcePackageRecipeData.sourcepackage_recipe_id,
And(
_SourcePackageRecipeDataInstruction.recipe_data_id ==
SourcePackageRecipeData.id,
- _SourcePackageRecipeDataInstruction.branch == branch)
+ insn_clause)
)
))
)
@classmethod
def createManifestFromText(cls, text, sourcepackage_recipe_build):
- """Create a manifest for the specified build.
-
- :param text: The text of the recipe to create a manifest for.
- :param sourcepackage_recipe_build: The build to associate the manifest
- with.
- :return: an instance of SourcePackageRecipeData.
- """
+ """See `ISourcePackageRecipeDataSource`."""
parsed = cls.getParsedRecipe(text)
return cls(
parsed, sourcepackage_recipe_build=sourcepackage_recipe_build)
@@ -216,7 +303,7 @@
def getRecipe(self):
"""The BaseRecipeBranch version of the recipe."""
base_branch = BaseRecipeBranch(
- self.base_branch.bzr_identity, self.deb_version_template,
+ self.base.identity, self.deb_version_template,
self.recipe_format, self.revspec)
insn_stack = []
for insn in self.instructions:
@@ -232,7 +319,7 @@
dict(insn=insn, recipe_branch=recipe_branch))
return base_branch
- def _scanInstructions(self, recipe_branch):
+ def _scanInstructions(self, base, recipe_branch):
"""Check the recipe_branch doesn't use 'run' and look up the branches.
We do all the lookups before we start constructing database objects to
@@ -241,15 +328,22 @@
:return: A map ``{branch_url: db_branch}``.
"""
r = {}
+ if IGitRepository.providedBy(base):
+ lookup = getUtility(IGitLookup)
+ missing_error = NoSuchGitRepository
+ private_error = PrivateGitRepositoryRecipe
+ else:
+ lookup = getUtility(IBranchLookup)
+ missing_error = NoSuchBranch
+ private_error = PrivateBranchRecipe
for instruction in recipe_branch.child_branches:
- db_branch = getUtility(IBranchLookup).getByUrl(
- instruction.recipe_branch.url)
+ db_branch = lookup.getByUrl(instruction.recipe_branch.url)
if db_branch is None:
- raise NoSuchBranch(instruction.recipe_branch.url)
+ raise missing_error(instruction.recipe_branch.url)
if db_branch.private:
- raise PrivateBranchRecipe(db_branch)
+ raise private_error(db_branch)
r[instruction.recipe_branch.url] = db_branch
- r.update(self._scanInstructions(instruction.recipe_branch))
+ r.update(self._scanInstructions(base, instruction.recipe_branch))
return r
def _recordInstructions(self, recipe_branch, parent_insn, branch_map,
@@ -274,10 +368,11 @@
"Unsupported instruction %r" % instruction)
line_number += 1
comment = None
- db_branch = branch_map[instruction.recipe_branch.url]
+ db_branch_or_repository = branch_map[instruction.recipe_branch.url]
insn = _SourcePackageRecipeDataInstruction(
instruction.recipe_branch.name, type, comment,
- line_number, db_branch, instruction.recipe_branch.revspec,
+ line_number, db_branch_or_repository,
+ instruction.recipe_branch.revspec,
nest_path, self, parent_insn, source_directory)
line_number = self._recordInstructions(
instruction.recipe_branch, insn, branch_map, line_number)
@@ -288,24 +383,33 @@
clear_property_cache(self)
if builder_recipe.format > MAX_RECIPE_FORMAT:
raise TooNewRecipeFormat(builder_recipe.format, MAX_RECIPE_FORMAT)
- branch_map = self._scanInstructions(builder_recipe)
+ base = getUtility(IBranchLookup).getByUrl(builder_recipe.url)
+ if base is None:
+ base = getUtility(IGitLookup).getByUrl(builder_recipe.url)
+ if base is None:
+ # If possible, try to raise an exception consistent with
+ # whether the current recipe is Bazaar-based or Git-based,
+ # so that error messages make more sense.
+ if self.base_git_repository is not None:
+ raise NoSuchGitRepository(builder_recipe.url)
+ else:
+ raise NoSuchBranch(builder_recipe.url)
+ elif base.private:
+ raise PrivateGitRepositoryRecipe(base)
+ elif base.private:
+ raise PrivateBranchRecipe(base)
+ branch_map = self._scanInstructions(base, builder_recipe)
# If this object hasn't been added to a store yet, there can't be any
# instructions linking to us yet.
if Store.of(self) is not None:
self.instructions.find().remove()
- branch_lookup = getUtility(IBranchLookup)
- base_branch = branch_lookup.getByUrl(builder_recipe.url)
- if base_branch is None:
- raise NoSuchBranch(builder_recipe.url)
- if base_branch.private:
- raise PrivateBranchRecipe(base_branch)
if builder_recipe.revspec is not None:
self.revspec = unicode(builder_recipe.revspec)
else:
self.revspec = None
self._recordInstructions(
builder_recipe, parent_insn=None, branch_map=branch_map)
- self.base_branch = base_branch
+ self.base = base
if builder_recipe.deb_version is None:
self.deb_version_template = None
else:
@@ -323,44 +427,72 @@
@staticmethod
def preLoadReferencedBranches(sourcepackagerecipedatas):
- # Circular import.
+ # Circular imports.
from lp.code.model.branchcollection import GenericBranchCollection
+ from lp.code.model.gitcollection import GenericGitCollection
# Load the related Branch, _SourcePackageRecipeDataInstruction.
base_branches = load_related(
Branch, sourcepackagerecipedatas, ['base_branch_id'])
+ base_repositories = load_related(
+ GitRepository, sourcepackagerecipedatas,
+ ['base_git_repository_id'])
sprd_instructions = load_referencing(
_SourcePackageRecipeDataInstruction,
sourcepackagerecipedatas, ['recipe_data_id'])
sub_branches = load_related(
Branch, sprd_instructions, ['branch_id'])
+ sub_repositories = load_related(
+ GitRepository, sprd_instructions, ['git_repository_id'])
all_branches = base_branches + sub_branches
- # Pre-load branches' data.
- GenericBranchCollection.preloadDataForBranches(all_branches)
+ all_repositories = base_repositories + sub_repositories
+ # Pre-load branches'/repositories' data.
+ if all_branches:
+ GenericBranchCollection.preloadDataForBranches(all_branches)
+ if all_repositories:
+ GenericGitCollection.preloadDataForRepositories(all_repositories)
# Store the pre-fetched objects on the sourcepackagerecipedatas
# objects.
- branch_to_recipe_data = dict([
- (instr.branch_id, instr.recipe_data_id)
- for instr in sprd_instructions])
- caches = dict((sprd.id, [sprd, get_property_cache(sprd)])
- for sprd in sourcepackagerecipedatas)
+ branch_to_recipe_data = {
+ instr.branch_id: instr.recipe_data_id
+ for instr in sprd_instructions
+ if instr.branch_id is not None}
+ repository_to_recipe_data = {
+ instr.git_repository_id: instr.recipe_data_id
+ for instr in sprd_instructions
+ if instr.git_repository_id is not None}
+ caches = {
+ sprd.id: [sprd, get_property_cache(sprd)]
+ for sprd in sourcepackagerecipedatas}
for unused, [sprd, cache] in caches.items():
- cache._referenced_branches = [sprd.base_branch]
+ cache._referenced_branches = [sprd.base]
for recipe_data_id, branches in groupby(
- sub_branches, lambda branch: branch_to_recipe_data[branch.id]):
+ sub_branches, lambda branch: branch_to_recipe_data[branch.id]):
cache = caches[recipe_data_id][1]
cache._referenced_branches.extend(list(branches))
+ for recipe_data_id, repositories in groupby(
+ sub_repositories,
+ lambda repository: repository_to_recipe_data[repository.id]):
+ cache = caches[recipe_data_id][1]
+ cache._referenced_branches.extend(list(repositories))
def getReferencedBranches(self):
- """Return an iterator of the Branch objects referenced by this recipe.
+ """Return an iterator of the Branch/GitRepository objects referenced
+ by this recipe.
"""
return self._referenced_branches
@cachedproperty
def _referenced_branches(self):
- referenced_branches = [self.base_branch]
+ referenced_branches = [self.base]
sub_branches = IStore(self).find(
Branch,
_SourcePackageRecipeDataInstruction.recipe_data == self,
Branch.id == _SourcePackageRecipeDataInstruction.branch_id)
referenced_branches.extend(sub_branches)
+ sub_repositories = IStore(self).find(
+ GitRepository,
+ _SourcePackageRecipeDataInstruction.recipe_data == self,
+ GitRepository.id ==
+ _SourcePackageRecipeDataInstruction.git_repository_id)
+ referenced_branches.extend(sub_repositories)
return referenced_branches
=== modified file 'lib/lp/code/model/tests/test_hasrecipes.py'
--- lib/lp/code/model/tests/test_hasrecipes.py 2015-09-16 13:26:12 +0000
+++ lib/lp/code/model/tests/test_hasrecipes.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2015 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for classes that implement IHasRecipes."""
@@ -39,6 +39,33 @@
self.factory.makeSourcePackageRecipe()
self.assertEqual(recipe, nonbase_branch.recipes.one())
+ def test_git_repository_implements_hasrecipes(self):
+ # Git repositories should implement IHasRecipes.
+ repository = self.factory.makeGitRepository()
+ self.assertProvides(repository, IHasRecipes)
+
+ def test_git_repository_recipes(self):
+ # IGitRepository.recipes should provide all the SourcePackageRecipes
+ # attached to that repository.
+ base_ref1, base_ref2 = self.factory.makeGitRefs(
+ paths=[u"refs/heads/ref1", u"refs/heads/ref2"])
+ [other_ref] = self.factory.makeGitRefs()
+ self.factory.makeSourcePackageRecipe(branches=[base_ref1])
+ self.factory.makeSourcePackageRecipe(branches=[base_ref2])
+ self.factory.makeSourcePackageRecipe(branches=[other_ref])
+ self.assertEqual(2, base_ref1.repository.recipes.count())
+
+ def test_git_repository_recipes_nonbase(self):
+ # IGitRepository.recipes should provide all the SourcePackageRecipes
+ # that refer to the repository, even as a non-base branch.
+ [base_ref] = self.factory.makeGitRefs()
+ [nonbase_ref] = self.factory.makeGitRefs()
+ [other_ref] = self.factory.makeGitRefs()
+ recipe = self.factory.makeSourcePackageRecipe(
+ branches=[base_ref, nonbase_ref])
+ self.factory.makeSourcePackageRecipe(branches=[other_ref])
+ self.assertEqual(recipe, nonbase_ref.repository.recipes.one())
+
def test_person_implements_hasrecipes(self):
# Person should implement IHasRecipes.
person = self.factory.makePerson()
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2015-09-12 00:23:59 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2016-01-12 04:06:05 +0000
@@ -32,13 +32,15 @@
from lp.code.errors import (
BuildAlreadyPending,
PrivateBranchRecipe,
+ PrivateGitRepositoryRecipe,
TooNewRecipeFormat,
)
from lp.code.interfaces.sourcepackagerecipe import (
ISourcePackageRecipe,
ISourcePackageRecipeSource,
ISourcePackageRecipeView,
- MINIMAL_RECIPE_TEXT,
+ MINIMAL_RECIPE_TEXT_BZR,
+ MINIMAL_RECIPE_TEXT_GIT,
)
from lp.code.interfaces.sourcepackagerecipebuild import (
ISourcePackageRecipeBuild,
@@ -86,14 +88,76 @@
from lp.testing.pages import webservice_for_person
-class TestSourcePackageRecipe(TestCaseWithFactory):
+class BzrMixin:
+ """Mixin for Bazaar-based recipe tests."""
+
+ private_error = PrivateBranchRecipe
+ branch_type = "branch"
+ recipe_id = "bzr-builder"
+
+ def makeBranch(self, **kwargs):
+ return self.factory.makeAnyBranch(**kwargs)
+
+ @staticmethod
+ def getRepository(branch):
+ return branch
+
+ @staticmethod
+ def getBranchRecipeText(branch):
+ return branch.identity
+
+ @staticmethod
+ def setInformationType(branch, information_type):
+ removeSecurityProxy(branch).information_type = information_type
+
+ def makeRecipeText(self):
+ branch = self.makeBranch()
+ return MINIMAL_RECIPE_TEXT_BZR % branch.identity
+
+
+class GitMixin:
+ """Mixin for Git-based recipe tests."""
+
+ private_error = PrivateGitRepositoryRecipe
+ branch_type = "repository"
+ recipe_id = "git-build-recipe"
+
+ def makeBranch(self, **kwargs):
+ return self.factory.makeGitRefs(**kwargs)[0]
+
+ @staticmethod
+ def getRepository(branch):
+ return branch.repository
+
+ @staticmethod
+ def getBranchRecipeText(branch):
+ return branch.identity
+
+ @staticmethod
+ def setInformationType(branch, information_type):
+ removeSecurityProxy(branch.repository).information_type = (
+ information_type)
+
+ def makeRecipeText(self):
+ branch = self.makeBranch()
+ return MINIMAL_RECIPE_TEXT_GIT % (
+ branch.repository.identity, branch.name)
+
+
+class TestSourcePackageRecipeMixin:
"""Tests for `SourcePackageRecipe` objects."""
layer = DatabaseFunctionalLayer
+ def makeSourcePackageRecipe(self, branches=(), recipe=None, **kwargs):
+ if recipe is None and len(branches) == 0:
+ branches = [self.makeBranch()]
+ return self.factory.makeSourcePackageRecipe(
+ branches=branches, recipe=recipe, **kwargs)
+
def test_implements_interface(self):
"""SourcePackageRecipe implements ISourcePackageRecipe."""
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
verifyObject(ISourcePackageRecipe, recipe)
def test_avoids_problematic_snapshots(self):
@@ -103,7 +167,7 @@
'pending_builds',
]
self.assertThat(
- self.factory.makeSourcePackageRecipe(),
+ self.makeSourcePackageRecipe(),
DoesNotSnapshot(problematic_properties, ISourcePackageRecipeView))
def makeRecipeComponents(self, branches=()):
@@ -128,7 +192,7 @@
components = self.makeRecipeComponents()
recipe = getUtility(ISourcePackageRecipeSource).new(**components)
transaction.commit()
- self.assertEquals(
+ self.assertEqual(
(components['registrant'], components['owner'],
set(components['distroseries']), components['name']),
(recipe.registrant, recipe.owner, set(recipe.distroseries),
@@ -139,35 +203,38 @@
"""An exception should be raised if the base branch is private."""
owner = self.factory.makePerson()
with person_logged_in(owner):
- branch = self.factory.makeAnyBranch(
+ branch = self.makeBranch(
owner=owner, information_type=InformationType.USERDATA)
components = self.makeRecipeComponents(branches=[branch])
recipe_source = getUtility(ISourcePackageRecipeSource)
e = self.assertRaises(
- PrivateBranchRecipe, recipe_source.new, **components)
+ self.private_error, recipe_source.new, **components)
self.assertEqual(
- 'Recipe may not refer to private branch: %s' %
- branch.bzr_identity, str(e))
+ 'Recipe may not refer to private %s: %s' %
+ (self.branch_type, self.getRepository(branch).identity),
+ str(e))
def test_creation_private_referenced_branch(self):
"""An exception should be raised if a referenced branch is private."""
owner = self.factory.makePerson()
with person_logged_in(owner):
- base_branch = self.factory.makeAnyBranch(owner=owner)
- referenced_branch = self.factory.makeAnyBranch(
+ base_branch = self.makeBranch(owner=owner)
+ referenced_branch = self.makeBranch(
owner=owner, information_type=InformationType.USERDATA)
branches = [base_branch, referenced_branch]
components = self.makeRecipeComponents(branches=branches)
recipe_source = getUtility(ISourcePackageRecipeSource)
e = self.assertRaises(
- PrivateBranchRecipe, recipe_source.new, **components)
+ self.private_error, recipe_source.new, **components)
self.assertEqual(
- 'Recipe may not refer to private branch: %s' %
- referenced_branch.bzr_identity, str(e))
+ 'Recipe may not refer to private %s: %s' % (
+ self.branch_type,
+ self.getRepository(referenced_branch).identity),
+ str(e))
def test_exists(self):
# Test ISourcePackageRecipeSource.exists
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
self.assertTrue(
getUtility(ISourcePackageRecipeSource).exists(
@@ -185,32 +252,33 @@
def test_recipe_implements_interface(self):
# SourcePackageRecipe objects implement ISourcePackageRecipe.
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
transaction.commit()
with person_logged_in(recipe.owner):
self.assertProvides(recipe, ISourcePackageRecipe)
def test_base_branch(self):
# When a recipe is created, we can access its base branch.
- branch = self.factory.makeAnyBranch()
- sp_recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
+ branch = self.makeBranch()
+ sp_recipe = self.makeSourcePackageRecipe(branches=[branch])
transaction.commit()
- self.assertEquals(branch, sp_recipe.base_branch)
+ self.assertEqual(self.getRepository(branch), sp_recipe.base)
def test_branch_links_created(self):
# When a recipe is created, we can query it for links to the branch
# it references.
- branch = self.factory.makeAnyBranch()
- sp_recipe = self.factory.makeSourcePackageRecipe(
- branches=[branch])
+ branch = self.makeBranch()
+ sp_recipe = self.makeSourcePackageRecipe(branches=[branch])
transaction.commit()
- self.assertEquals([branch], list(sp_recipe.getReferencedBranches()))
+ self.assertEqual(
+ [self.getRepository(branch)],
+ list(sp_recipe.getReferencedBranches()))
def createSourcePackageRecipe(self, number_of_branches=2):
branches = []
for i in range(number_of_branches):
- branches.append(self.factory.makeAnyBranch())
- sp_recipe = self.factory.makeSourcePackageRecipe(branches=branches)
+ branches.append(self.makeBranch())
+ sp_recipe = self.makeSourcePackageRecipe(branches=branches)
transaction.commit()
return sp_recipe, branches
@@ -218,8 +286,8 @@
# If a recipe links to more than one branch, getReferencedBranches()
# returns all of them.
sp_recipe, [branch1, branch2] = self.createSourcePackageRecipe()
- self.assertEquals(
- sorted([branch1, branch2]),
+ self.assertEqual(
+ sorted([self.getRepository(branch1), self.getRepository(branch2)]),
sorted(sp_recipe.getReferencedBranches()))
def test_preLoadReferencedBranches(self):
@@ -230,16 +298,15 @@
referenced_branches = sp_recipe.getReferencedBranches()
clear_property_cache(recipe_data)
SourcePackageRecipeData.preLoadReferencedBranches([recipe_data])
- self.assertEquals(
+ self.assertEqual(
sorted(referenced_branches),
sorted(sp_recipe.getReferencedBranches()))
def test_random_user_cant_edit(self):
# An arbitrary user can't set attributes.
- branch1 = self.factory.makeAnyBranch()
+ branch1 = self.makeBranch()
recipe_1 = self.factory.makeRecipeText(branch1)
- sp_recipe = self.factory.makeSourcePackageRecipe(
- recipe=recipe_1)
+ sp_recipe = self.makeSourcePackageRecipe(recipe=recipe_1)
login_person(self.factory.makePerson())
self.assertRaises(
Unauthorized, getattr, sp_recipe, 'setRecipeText')
@@ -247,71 +314,78 @@
def test_set_recipe_text_resets_branch_references(self):
# When the recipe_text is replaced, getReferencedBranches returns
# (only) the branches referenced by the new recipe.
- branch1 = self.factory.makeAnyBranch()
- sp_recipe = self.factory.makeSourcePackageRecipe(
- branches=[branch1])
- branch2 = self.factory.makeAnyBranch()
+ branch1 = self.makeBranch()
+ sp_recipe = self.makeSourcePackageRecipe(branches=[branch1])
+ branch2 = self.makeBranch()
new_recipe = self.factory.makeRecipeText(branch2)
with person_logged_in(sp_recipe.owner):
sp_recipe.setRecipeText(new_recipe)
- self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))
+ self.assertEqual(
+ [self.getRepository(branch2)],
+ list(sp_recipe.getReferencedBranches()))
def test_rejects_run_command(self):
recipe_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s
run touch test
- ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
+ ''' % dict(recipe_id=self.recipe_id,
+ base=self.getBranchRecipeText(self.makeBranch()))
recipe_text = textwrap.dedent(recipe_text)
self.assertRaises(
- ForbiddenInstructionError, self.factory.makeSourcePackageRecipe,
+ ForbiddenInstructionError, self.makeSourcePackageRecipe,
recipe=recipe_text)
def test_run_rejected_without_mangling_recipe(self):
- sp_recipe = self.factory.makeSourcePackageRecipe()
+ sp_recipe = self.makeSourcePackageRecipe()
old_branches = list(sp_recipe.getReferencedBranches())
recipe_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s
run touch test
- ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
+ ''' % dict(recipe_id=self.recipe_id,
+ base=self.getBranchRecipeText(self.makeBranch()))
recipe_text = textwrap.dedent(recipe_text)
with person_logged_in(sp_recipe.owner):
self.assertRaises(
ForbiddenInstructionError, sp_recipe.setRecipeText,
recipe_text)
- self.assertEquals(
+ self.assertEqual(
old_branches, list(sp_recipe.getReferencedBranches()))
def test_nest_part(self):
"""nest-part instruction can be round-tripped."""
- base = self.factory.makeBranch()
- nested = self.factory.makeBranch()
+ base = self.makeBranch()
+ nested = self.makeBranch()
recipe_text = (
- "# bzr-builder format 0.3 deb-version 1\n"
+ "# %s format 0.3 deb-version 1\n"
"%s revid:base_revid\n"
"nest-part nested1 %s foo bar tag:foo\n" %
- (base.bzr_identity, nested.bzr_identity))
- recipe = self.factory.makeSourcePackageRecipe(recipe=recipe_text)
+ (self.recipe_id,
+ self.getRepository(base).identity,
+ self.getRepository(nested).identity))
+ recipe = self.makeSourcePackageRecipe(recipe=recipe_text)
self.assertEqual(recipe_text, recipe.recipe_text)
def test_nest_part_no_target(self):
"""nest-part instruction with no target-dir can be round-tripped."""
- base = self.factory.makeBranch()
- nested = self.factory.makeBranch()
+ base = self.makeBranch()
+ nested = self.makeBranch()
recipe_text = (
- "# bzr-builder format 0.3 deb-version 1\n"
+ "# %s format 0.3 deb-version 1\n"
"%s revid:base_revid\n"
"nest-part nested1 %s foo\n" %
- (base.bzr_identity, nested.bzr_identity))
- recipe = self.factory.makeSourcePackageRecipe(recipe=recipe_text)
+ (self.recipe_id,
+ self.getRepository(base).identity,
+ self.getRepository(nested).identity))
+ recipe = self.makeSourcePackageRecipe(recipe=recipe_text)
self.assertEqual(recipe_text, recipe.recipe_text)
def test_accept_format_0_3(self):
"""Recipe format 0.3 is accepted."""
builder_recipe = self.factory.makeRecipe()
builder_recipe.format = 0.3
- self.factory.makeSourcePackageRecipe(recipe=str(builder_recipe))
+ self.makeSourcePackageRecipe(recipe=str(builder_recipe))
def test_reject_newer_formats(self):
with recipe_parser_newest_version(145.115):
@@ -319,11 +393,11 @@
builder_recipe.format = 145.115
self.assertRaises(
TooNewRecipeFormat,
- self.factory.makeSourcePackageRecipe,
+ self.makeSourcePackageRecipe,
recipe=str(builder_recipe))
def test_requestBuild(self):
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
(distroseries,) = list(recipe.distroseries)
ppa = self.factory.makeArchive()
build = recipe.requestBuild(ppa, ppa.owner, distroseries,
@@ -342,17 +416,17 @@
removeSecurityProxy(build).build_farm_job_id).one()
self.assertProvides(build_queue, IBuildQueue)
self.assertTrue(build_queue.virtualized)
- self.assertEquals(build_queue.status, BuildQueueStatus.WAITING)
+ self.assertEqual(build_queue.status, BuildQueueStatus.WAITING)
def test_requestBuildRejectsNotPPA(self):
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
not_ppa = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
(distroseries,) = list(recipe.distroseries)
self.assertRaises(NonPPABuildRequest, recipe.requestBuild, not_ppa,
not_ppa.owner, distroseries, PackagePublishingPocket.RELEASE)
def test_requestBuildRejectsNoPermission(self):
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
ppa = self.factory.makeArchive()
requester = self.factory.makePerson()
(distroseries,) = list(recipe.distroseries)
@@ -360,14 +434,14 @@
requester, distroseries, PackagePublishingPocket.RELEASE)
def test_requestBuildRejectsInvalidPocket(self):
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
ppa = self.factory.makeArchive()
(distroseries,) = list(recipe.distroseries)
self.assertRaises(InvalidPocketForPPA, recipe.requestBuild, ppa,
ppa.owner, distroseries, PackagePublishingPocket.BACKPORTS)
def test_requestBuildRejectsDisabledArchive(self):
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
ppa = self.factory.makeArchive()
removeSecurityProxy(ppa).disable()
(distroseries,) = list(recipe.distroseries)
@@ -377,7 +451,7 @@
def test_requestBuildScore(self):
"""Normal build requests have a relatively low queue score (2505)."""
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
build = recipe.requestBuild(recipe.daily_build_archive,
recipe.owner, list(recipe.distroseries)[0],
PackagePublishingPocket.RELEASE)
@@ -387,7 +461,7 @@
def test_requestBuildManualScore(self):
"""Manual build requests have a score equivalent to binary builds."""
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
build = recipe.requestBuild(recipe.daily_build_archive,
recipe.owner, list(recipe.distroseries)[0],
PackagePublishingPocket.RELEASE, manual=True)
@@ -397,7 +471,7 @@
def test_requestBuild_relative_build_score(self):
"""Offsets for archives are respected."""
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
archive = recipe.daily_build_archive
removeSecurityProxy(archive).relative_build_score = 100
build = recipe.requestBuild(
@@ -409,7 +483,7 @@
def test_requestBuildRejectRepeats(self):
"""Reject build requests that are identical to pending builds."""
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
series = list(recipe.distroseries)[0]
archive = self.factory.makeArchive(owner=recipe.owner)
old_build = recipe.requestBuild(archive, recipe.owner, series,
@@ -447,7 +521,7 @@
private=True)
# Create a recipe with the team P3A as the build destination.
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
# Add upload component rights for the non-team person.
with person_logged_in(team_owner):
@@ -467,13 +541,13 @@
def test_sourcepackagerecipe_description(self):
"""Ensure that the SourcePackageRecipe has a proper description."""
description = u'The whoozits and whatzits.'
- source_package_recipe = self.factory.makeSourcePackageRecipe(
+ source_package_recipe = self.makeSourcePackageRecipe(
description=description)
self.assertEqual(description, source_package_recipe.description)
def test_distroseries(self):
"""Test that the distroseries behaves as a set."""
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
distroseries = self.factory.makeDistroSeries()
(old_distroseries,) = recipe.distroseries
recipe.distroseries.add(distroseries)
@@ -486,7 +560,7 @@
def test_build_daily(self):
"""Test that build_daily behaves as a bool."""
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
self.assertFalse(recipe.build_daily)
login_person(recipe.owner)
recipe.build_daily = True
@@ -495,9 +569,9 @@
def test_view_public(self):
"""Anyone can view a recipe with public branches."""
owner = self.factory.makePerson()
- branch = self.factory.makeAnyBranch(owner=owner)
+ branch = self.makeBranch(owner=owner)
with person_logged_in(owner):
- recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
+ recipe = self.makeSourcePackageRecipe(branches=[branch])
self.assertTrue(check_permission('launchpad.View', recipe))
with person_logged_in(self.factory.makePerson()):
self.assertTrue(check_permission('launchpad.View', recipe))
@@ -506,19 +580,18 @@
def test_view_private(self):
"""Recipes with private branches are restricted."""
owner = self.factory.makePerson()
- branch = self.factory.makeAnyBranch(owner=owner)
+ branch = self.makeBranch(owner=owner)
with person_logged_in(owner):
- recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
+ recipe = self.makeSourcePackageRecipe(branches=[branch])
self.assertTrue(check_permission('launchpad.View', recipe))
- removeSecurityProxy(branch).information_type = (
- InformationType.USERDATA)
+ self.setInformationType(branch, InformationType.USERDATA)
with person_logged_in(self.factory.makePerson()):
self.assertFalse(check_permission('launchpad.View', recipe))
self.assertFalse(check_permission('launchpad.View', recipe))
def test_edit(self):
"""Only the owner can edit a sourcepackagerecipe."""
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
self.assertFalse(check_permission('launchpad.Edit', recipe))
with person_logged_in(self.factory.makePerson()):
self.assertFalse(check_permission('launchpad.Edit', recipe))
@@ -528,8 +601,8 @@
def test_destroySelf(self):
"""Should destroy associated builds, distroseries, etc."""
# Recipe should have at least one datainstruction.
- branches = [self.factory.makeBranch() for count in range(2)]
- recipe = self.factory.makeSourcePackageRecipe(branches=branches)
+ branches = [self.makeBranch() for count in range(2)]
+ recipe = self.makeSourcePackageRecipe(branches=branches)
pending_build = self.factory.makeSourcePackageRecipeBuild(
recipe=recipe)
pending_build.queueBuild()
@@ -545,7 +618,7 @@
def test_destroySelf_preserves_release(self):
# Destroying a sourcepackagerecipe removes references to its builds
# from their releases.
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
release = self.factory.makeSourcePackageRelease(
source_package_recipe_build=build)
@@ -557,7 +630,7 @@
def test_destroySelf_retains_build(self):
# Destroying a sourcepackagerecipe removes references to its builds
# from their releases.
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
store = Store.of(build)
store.flush()
@@ -578,12 +651,11 @@
def test_findStaleDailyBuilds(self):
# Stale recipe not built daily.
- self.factory.makeSourcePackageRecipe()
+ self.makeSourcePackageRecipe()
# Daily build recipe not stale.
- self.factory.makeSourcePackageRecipe(
- build_daily=True, is_stale=False)
+ self.makeSourcePackageRecipe(build_daily=True, is_stale=False)
# Stale daily build.
- stale_daily = self.factory.makeSourcePackageRecipe(
+ stale_daily = self.makeSourcePackageRecipe(
build_daily=True, is_stale=True)
self.assertContentEqual([stale_daily],
SourcePackageRecipe.findStaleDailyBuilds())
@@ -591,8 +663,7 @@
def test_findStaleDailyBuildsDistinct(self):
# If a recipe has 2 builds due to 2 distroseries, it only returns
# one recipe.
- recipe = self.factory.makeSourcePackageRecipe(
- build_daily=True, is_stale=True)
+ recipe = self.makeSourcePackageRecipe(build_daily=True, is_stale=True)
hoary = self.factory.makeSourcePackageRecipeDistroseries("hoary")
recipe.distroseries.add(hoary)
for series in recipe.distroseries:
@@ -613,7 +684,7 @@
build.updateStatus(
BuildStatus.FULLYBUILT,
date_finished=build.date_started + duration)
- recipe = removeSecurityProxy(self.factory.makeSourcePackageRecipe())
+ recipe = removeSecurityProxy(self.makeSourcePackageRecipe())
self.assertIs(None, recipe.getMedianBuildDuration())
build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
set_duration(build, 10)
@@ -632,7 +703,7 @@
def test_getBuilds(self):
# Test the various getBuilds methods.
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
builds = [
self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
for x in range(3)]
@@ -654,7 +725,7 @@
person = self.factory.makePerson()
archives = [self.factory.makeArchive(owner=person) for x in range(4)]
distroseries = self.factory.makeSourcePackageRecipeDistroseries()
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
build_info = []
for archive in archives:
@@ -666,7 +737,7 @@
def test_getBuilds_cancelled(self):
# Cancelled builds are not considered pending.
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
with admin_logged_in():
build.queueBuild()
@@ -676,40 +747,42 @@
self.assertEqual([], list(recipe.pending_builds))
def test_setRecipeText_private_base_branch(self):
- source_package_recipe = self.factory.makeSourcePackageRecipe()
+ source_package_recipe = self.makeSourcePackageRecipe()
with person_logged_in(source_package_recipe.owner):
- branch = self.factory.makeAnyBranch(
+ branch = self.makeBranch(
owner=source_package_recipe.owner,
information_type=InformationType.USERDATA)
recipe_text = self.factory.makeRecipeText(branch)
e = self.assertRaises(
- PrivateBranchRecipe, source_package_recipe.setRecipeText,
+ self.private_error, source_package_recipe.setRecipeText,
recipe_text)
self.assertEqual(
- 'Recipe may not refer to private branch: %s' %
- branch.bzr_identity, str(e))
+ 'Recipe may not refer to private %s: %s' %
+ (self.branch_type, self.getRepository(branch).identity),
+ str(e))
def test_setRecipeText_private_referenced_branch(self):
- source_package_recipe = self.factory.makeSourcePackageRecipe()
+ source_package_recipe = self.makeSourcePackageRecipe()
with person_logged_in(source_package_recipe.owner):
- base_branch = self.factory.makeAnyBranch(
- owner=source_package_recipe.owner)
- referenced_branch = self.factory.makeAnyBranch(
+ base_branch = self.makeBranch(owner=source_package_recipe.owner)
+ referenced_branch = self.makeBranch(
owner=source_package_recipe.owner,
information_type=InformationType.USERDATA)
recipe_text = self.factory.makeRecipeText(
base_branch, referenced_branch)
e = self.assertRaises(
- PrivateBranchRecipe, source_package_recipe.setRecipeText,
+ self.private_error, source_package_recipe.setRecipeText,
recipe_text)
self.assertEqual(
- 'Recipe may not refer to private branch: %s' %
- referenced_branch.bzr_identity, str(e))
+ 'Recipe may not refer to private %s: %s' %
+ (self.branch_type,
+ self.getRepository(referenced_branch).identity),
+ str(e))
def test_getBuilds_ignores_disabled_archive(self):
# Builds into a disabled archive aren't returned.
archive = self.factory.makeArchive()
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
self.factory.makeSourcePackageRecipeBuild(
recipe=recipe, archive=archive)
with person_logged_in(archive.owner):
@@ -719,19 +792,19 @@
self.assertEqual([], list(recipe.pending_builds))
def test_containsUnbuildableSeries(self):
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
self.assertFalse(recipe.containsUnbuildableSeries(
recipe.daily_build_archive))
def test_containsUnbuildableSeries_with_obsolete_series(self):
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
warty = self.factory.makeSourcePackageRecipeDistroseries()
removeSecurityProxy(warty).status = SeriesStatus.OBSOLETE
self.assertTrue(recipe.containsUnbuildableSeries(
recipe.daily_build_archive))
def test_performDailyBuild_filters_obsolete_series(self):
- recipe = self.factory.makeSourcePackageRecipe()
+ recipe = self.makeSourcePackageRecipe()
warty = self.factory.makeSourcePackageRecipeDistroseries()
hoary = self.factory.makeSourcePackageRecipeDistroseries(name='hoary')
with person_logged_in(recipe.owner):
@@ -741,19 +814,30 @@
self.assertEqual([build.recipe for build in builds], [recipe])
-class TestRecipeBranchRoundTripping(TestCaseWithFactory):
+class TestSourcePackageRecipeBzr(
+ TestSourcePackageRecipeMixin, BzrMixin, TestCaseWithFactory):
+ """Test `SourcePackageRecipe` objects for Bazaar."""
+
+
+class TestSourcePackageRecipeGit(
+ TestSourcePackageRecipeMixin, GitMixin, TestCaseWithFactory):
+ """Test `SourcePackageRecipe` objects for Git."""
+
+
+class TestRecipeBranchRoundTrippingMixin:
layer = DatabaseFunctionalLayer
def setUp(self):
- super(TestRecipeBranchRoundTripping, self).setUp()
- self.base_branch = self.factory.makeAnyBranch()
- self.nested_branch = self.factory.makeAnyBranch()
- self.merged_branch = self.factory.makeAnyBranch()
+ super(TestRecipeBranchRoundTrippingMixin, self).setUp()
+ self.base_branch = self.makeBranch()
+ self.nested_branch = self.makeBranch()
+ self.merged_branch = self.makeBranch()
self.branch_identities = {
- 'base': self.base_branch.bzr_identity,
- 'nested': self.nested_branch.bzr_identity,
- 'merged': self.merged_branch.bzr_identity,
+ 'recipe_id': self.recipe_id,
+ 'base': self.getRepository(self.base_branch).identity,
+ 'nested': self.getRepository(self.nested_branch).identity,
+ 'merged': self.getRepository(self.merged_branch).identity,
}
def get_recipe(self, recipe_text):
@@ -785,161 +869,173 @@
def test_builds_simplest_recipe(self):
recipe_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s
''' % self.branch_identities
base_branch = self.get_recipe(recipe_text).builder_recipe
self.check_base_recipe_branch(
- base_branch, self.base_branch.bzr_identity,
+ base_branch, self.getRepository(self.base_branch).identity,
deb_version='0.1-{revno}')
def test_builds_recipe_with_merge(self):
recipe_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s
merge bar %(merged)s
''' % self.branch_identities
base_branch = self.get_recipe(recipe_text).builder_recipe
self.check_base_recipe_branch(
- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
- deb_version='0.1-{revno}')
+ base_branch, self.getRepository(self.base_branch).identity,
+ num_child_branches=1, deb_version='0.1-{revno}')
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(
- child_branch, "bar", self.merged_branch.bzr_identity)
+ child_branch, "bar",
+ self.getRepository(self.merged_branch).identity)
def test_builds_recipe_with_nest(self):
recipe_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s
nest bar %(nested)s baz
''' % self.branch_identities
base_branch = self.get_recipe(recipe_text).builder_recipe
self.check_base_recipe_branch(
- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
- deb_version='0.1-{revno}')
+ base_branch, self.getRepository(self.base_branch).identity,
+ num_child_branches=1, deb_version='0.1-{revno}')
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(
- child_branch, "bar", self.nested_branch.bzr_identity)
+ child_branch, "bar",
+ self.getRepository(self.nested_branch).identity)
def test_builds_recipe_with_nest_then_merge(self):
recipe_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s
nest bar %(nested)s baz
merge zam %(merged)s
''' % self.branch_identities
base_branch = self.get_recipe(recipe_text).builder_recipe
self.check_base_recipe_branch(
- base_branch, self.base_branch.bzr_identity, num_child_branches=2,
- deb_version='0.1-{revno}')
+ base_branch, self.getRepository(self.base_branch).identity,
+ num_child_branches=2, deb_version='0.1-{revno}')
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(
- child_branch, "bar", self.nested_branch.bzr_identity)
+ child_branch, "bar",
+ self.getRepository(self.nested_branch).identity)
child_branch, location = base_branch.child_branches[1].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(
- child_branch, "zam", self.merged_branch.bzr_identity)
+ child_branch, "zam",
+ self.getRepository(self.merged_branch).identity)
def test_builds_recipe_with_merge_then_nest(self):
recipe_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s
merge zam %(merged)s
nest bar %(nested)s baz
''' % self.branch_identities
base_branch = self.get_recipe(recipe_text).builder_recipe
self.check_base_recipe_branch(
- base_branch, self.base_branch.bzr_identity, num_child_branches=2,
- deb_version='0.1-{revno}')
+ base_branch, self.getRepository(self.base_branch).identity,
+ num_child_branches=2, deb_version='0.1-{revno}')
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(
- child_branch, "zam", self.merged_branch.bzr_identity)
+ child_branch, "zam",
+ self.getRepository(self.merged_branch).identity)
child_branch, location = base_branch.child_branches[1].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(
- child_branch, "bar", self.nested_branch.bzr_identity)
+ child_branch, "bar",
+ self.getRepository(self.nested_branch).identity)
def test_builds_a_merge_in_to_a_nest(self):
recipe_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s
nest bar %(nested)s baz
merge zam %(merged)s
''' % self.branch_identities
base_branch = self.get_recipe(recipe_text).builder_recipe
self.check_base_recipe_branch(
- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
- deb_version='0.1-{revno}')
+ base_branch, self.getRepository(self.base_branch).identity,
+ num_child_branches=1, deb_version='0.1-{revno}')
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(
- child_branch, "bar", self.nested_branch.bzr_identity,
+ child_branch, "bar",
+ self.getRepository(self.nested_branch).identity,
num_child_branches=1)
child_branch, location = child_branch.child_branches[0].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(
- child_branch, "zam", self.merged_branch.bzr_identity)
+ child_branch, "zam",
+ self.getRepository(self.merged_branch).identity)
def tests_builds_nest_into_a_nest(self):
- nested2 = self.factory.makeAnyBranch()
- self.branch_identities['nested2'] = nested2.bzr_identity
+ nested2 = self.makeBranch()
+ self.branch_identities['nested2'] = (
+ self.getRepository(nested2).identity)
recipe_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s
nest bar %(nested)s baz
nest zam %(nested2)s zoo
''' % self.branch_identities
base_branch = self.get_recipe(recipe_text).builder_recipe
self.check_base_recipe_branch(
- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
- deb_version='0.1-{revno}')
+ base_branch, self.getRepository(self.base_branch).identity,
+ num_child_branches=1, deb_version='0.1-{revno}')
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(
- child_branch, "bar", self.nested_branch.bzr_identity,
+ child_branch, "bar",
+ self.getRepository(self.nested_branch).identity,
num_child_branches=1)
child_branch, location = child_branch.child_branches[0].as_tuple()
self.assertEqual("zoo", location)
- self.check_recipe_branch(child_branch, "zam", nested2.bzr_identity)
+ self.check_recipe_branch(
+ child_branch, "zam", self.getRepository(nested2).identity)
def tests_builds_recipe_with_revspecs(self):
recipe_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s revid:a
nest bar %(nested)s baz tag:b
merge zam %(merged)s 2
''' % self.branch_identities
base_branch = self.get_recipe(recipe_text).builder_recipe
self.check_base_recipe_branch(
- base_branch, self.base_branch.bzr_identity, num_child_branches=2,
- revspec="revid:a", deb_version='0.1-{revno}')
+ base_branch, self.getRepository(self.base_branch).identity,
+ num_child_branches=2, revspec="revid:a", deb_version='0.1-{revno}')
instruction = base_branch.child_branches[0]
child_branch = instruction.recipe_branch
location = instruction.nest_path
self.assertEqual("baz", location)
self.check_recipe_branch(
- child_branch, "bar", self.nested_branch.bzr_identity,
- revspec="tag:b")
+ child_branch, "bar",
+ self.getRepository(self.nested_branch).identity, revspec="tag:b")
child_branch, location = base_branch.child_branches[1].as_tuple()
self.assertEqual(None, location)
self.check_recipe_branch(
- child_branch, "zam", self.merged_branch.bzr_identity, revspec="2")
+ child_branch, "zam",
+ self.getRepository(self.merged_branch).identity, revspec="2")
def test_unsets_revspecs(self):
# Changing a recipe's text to no longer include revspecs unsets
# them from the stored copy.
revspec_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s revid:a
nest bar %(nested)s baz tag:b
merge zam %(merged)s 2
''' % self.branch_identities
no_revspec_text = '''\
- # bzr-builder format 0.3 deb-version 0.1-{revno}
+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
%(base)s
nest bar %(nested)s baz
merge zam %(merged)s
@@ -952,18 +1048,29 @@
def test_builds_recipe_without_debversion(self):
recipe_text = '''\
- # bzr-builder format 0.4
+ # %(recipe_id)s format 0.4
%(base)s
nest bar %(nested)s baz
''' % self.branch_identities
base_branch = self.get_recipe(recipe_text).builder_recipe
self.check_base_recipe_branch(
- base_branch, self.base_branch.bzr_identity, num_child_branches=1,
- deb_version=None)
+ base_branch, self.getRepository(self.base_branch).identity,
+ num_child_branches=1, deb_version=None)
child_branch, location = base_branch.child_branches[0].as_tuple()
self.assertEqual("baz", location)
self.check_recipe_branch(
- child_branch, "bar", self.nested_branch.bzr_identity)
+ child_branch, "bar",
+ self.getRepository(self.nested_branch).identity)
+
+
+class TestRecipeBranchRoundTrippingBzr(
+ TestRecipeBranchRoundTrippingMixin, BzrMixin, TestCaseWithFactory):
+ pass
+
+
+class TestRecipeBranchRoundTrippingGit(
+ TestRecipeBranchRoundTrippingMixin, GitMixin, TestCaseWithFactory):
+ pass
class RecipeDateLastModified(TestCaseWithFactory):
@@ -991,14 +1098,10 @@
self.recipe, 'date_last_modified', UTC_NOW)
-class TestWebservice(TestCaseWithFactory):
+class TestWebserviceMixin:
layer = AppServerLayer
- def makeRecipeText(self):
- branch = self.factory.makeBranch()
- return MINIMAL_RECIPE_TEXT % branch.bzr_identity
-
def makeRecipe(self, user=None, owner=None, recipe_text=None,
version='devel'):
# rockstar 21 Jul 2010 - This function does more commits than I'd
@@ -1172,3 +1275,11 @@
with StormStatementRecorder() as recorder:
webservice.get(url)
self.assertThat(recorder, HasQueryCount(Equals(23)))
+
+
+class TestWebserviceBzr(TestWebserviceMixin, BzrMixin, TestCaseWithFactory):
+ pass
+
+
+class TestWebserviceGit(TestWebserviceMixin, GitMixin, TestCaseWithFactory):
+ pass
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2015-10-19 10:56:16 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2013 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for source package builds."""
@@ -106,11 +106,11 @@
self.assertEqual(bq, spb.buildqueue_record)
def test_title(self):
- # A recipe build's title currently consists of the base
- # branch's unique name.
+ # A recipe build's title currently consists of the base source
+ # location's unique name.
spb = self.makeSourcePackageRecipeBuild()
title = "%s recipe build in %s %s" % (
- spb.recipe.base_branch.unique_name, spb.distribution.name,
+ spb.recipe.base.unique_name, spb.distribution.name,
spb.distroseries.name)
self.assertEqual(spb.title, title)
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2015-10-01 17:32:41 +0000
+++ lib/lp/registry/model/product.py 2016-01-12 04:06:05 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Database classes including and related to Product."""
@@ -42,6 +42,7 @@
Or,
Select,
SQL,
+ Union,
)
from storm.locals import (
Store,
@@ -122,6 +123,7 @@
from lp.code.interfaces.gitrepository import IGitRepositorySet
from lp.code.model.branch import Branch
from lp.code.model.branchnamespace import BRANCH_POLICY_ALLOWED_TYPES
+from lp.code.model.gitrepository import GitRepository
from lp.code.model.hasbranches import (
HasBranchesMixin,
HasCodeImportsMixin,
@@ -1578,8 +1580,18 @@
SourcePackageRecipe,
SourcePackageRecipe.id ==
SourcePackageRecipeData.sourcepackage_recipe_id,
- SourcePackageRecipeData.base_branch == Branch.id,
- Branch.product == self)
+ SourcePackageRecipeData.id.is_in(Union(
+ Select(
+ SourcePackageRecipeData.id,
+ And(
+ SourcePackageRecipeData.base_branch == Branch.id,
+ Branch.product == self)),
+ Select(
+ SourcePackageRecipeData.id,
+ And(
+ SourcePackageRecipeData.base_git_repository ==
+ GitRepository.id,
+ GitRepository.project == self)))))
hook = SourcePackageRecipe.preLoadDataForSourcePackageRecipes
return DecoratedResultSet(recipes, pre_iter_hook=hook)
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2015-10-13 13:22:08 +0000
+++ lib/lp/testing/factory.py 2016-01-12 04:06:05 +0000
@@ -2,7 +2,7 @@
# NOTE: The first line above must stay first; do not move the copyright
# notice to the top. See http://www.python.org/dev/peps/pep-0263/.
#
-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Testing infrastructure for the Launchpad application.
@@ -112,6 +112,7 @@
RevisionControlSystems,
)
from lp.code.errors import UnknownBranchTypeError
+from lp.code.interfaces.branch import IBranch
from lp.code.interfaces.branchnamespace import get_branch_namespace
from lp.code.interfaces.branchtarget import IBranchTarget
from lp.code.interfaces.codeimport import ICodeImportSet
@@ -119,11 +120,13 @@
from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet
from lp.code.interfaces.codeimportresult import ICodeImportResultSet
from lp.code.interfaces.gitnamespace import get_git_namespace
+from lp.code.interfaces.gitref import IGitRef
from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
from lp.code.interfaces.revision import IRevisionSet
from lp.code.interfaces.sourcepackagerecipe import (
ISourcePackageRecipeSource,
- MINIMAL_RECIPE_TEXT,
+ MINIMAL_RECIPE_TEXT_BZR,
+ MINIMAL_RECIPE_TEXT_GIT,
)
from lp.code.interfaces.sourcepackagerecipebuild import (
ISourcePackageRecipeBuildSource,
@@ -2897,9 +2900,22 @@
branches = (self.makeAnyBranch(), )
base_branch = branches[0]
other_branches = branches[1:]
- text = MINIMAL_RECIPE_TEXT % base_branch.bzr_identity
+ if IBranch.providedBy(base_branch):
+ text = MINIMAL_RECIPE_TEXT_BZR % base_branch.identity
+ elif IGitRef.providedBy(base_branch):
+ text = MINIMAL_RECIPE_TEXT_GIT % (
+ base_branch.repository.identity, base_branch.name)
+ else:
+ raise AssertionError(
+ "Unsupported base_branch: %r" % (base_branch,))
for i, branch in enumerate(other_branches):
- text += 'merge dummy-%s %s\n' % (i, branch.bzr_identity)
+ if IBranch.providedBy(branch):
+ text += 'merge dummy-%s %s\n' % (i, branch.identity)
+ elif IGitRef.providedBy(branch):
+ text += 'merge dummy-%s %s %s\n' % (
+ i, branch.repository.identity, branch.name)
+ else:
+ raise AssertionError("Unsupported branch: %r" % (branch,))
return text
def makeRecipe(self, *branches):
Follow ups