Merge lp:~cjwatson/launchpad/recipe-ambiguous-vcs into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18737
Proposed branch: lp:~cjwatson/launchpad/recipe-ambiguous-vcs
Merge into: lp:launchpad
Diff against target: 290 lines (+92/-35)
6 files modified
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+2/-5)
lib/lp/code/interfaces/sourcepackagerecipe.py (+17/-2)
lib/lp/code/model/sourcepackagerecipe.py (+6/-5)
lib/lp/code/model/sourcepackagerecipebuild.py (+3/-2)
lib/lp/code/model/sourcepackagerecipedata.py (+25/-19)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+39/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/recipe-ambiguous-vcs
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+350699@code.launchpad.net

Commit message

Handle the case where a Bazaar branch and a Git repository have the same identity URL when creating a recipe.

Description of the change

There are still various bodges because we're using bzr-builder to parse git-build-recipe recipes, but passing through the type should smooth over the rough edges.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2018-01-02 16:10:26 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2018-07-23 19:40:00 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010-2017 Canonical Ltd. This software is licensed under the1# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for the source package recipe view classes and templates."""4"""Tests for the source package recipe view classes and templates."""
@@ -551,12 +551,9 @@
551 # branch location, they should get an error.551 # branch location, they should get an error.
552 browser = self.createRecipe(552 browser = self.createRecipe(
553 self.minimal_recipe_text.splitlines()[0] + '\nfoo\n')553 self.minimal_recipe_text.splitlines()[0] + '\nfoo\n')
554 # This page doesn't know whether the user was aiming for a Bazaar
555 # branch or a Git repository; the error message always says
556 # "branch".
557 self.assertEqual(554 self.assertEqual(
558 get_feedback_messages(browser.contents)[1],555 get_feedback_messages(browser.contents)[1],
559 'foo is not a branch on Launchpad.')556 'foo %s' % self.no_such_object_message)
560557
561 def test_create_recipe_bad_instruction_branch(self):558 def test_create_recipe_bad_instruction_branch(self):
562 # If a user tries to create source package recipe with a bad559 # If a user tries to create source package recipe with a bad
563560
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
--- lib/lp/code/interfaces/sourcepackagerecipe.py 2017-06-16 10:02:47 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2018-07-23 19:40:00 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2017 Canonical Ltd. This software is licensed under the1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Interface of the `SourcePackageRecipe` content type."""4"""Interface of the `SourcePackageRecipe` content type."""
@@ -15,11 +15,16 @@
15 'ISourcePackageRecipeSource',15 'ISourcePackageRecipeSource',
16 'MINIMAL_RECIPE_TEXT_BZR',16 'MINIMAL_RECIPE_TEXT_BZR',
17 'MINIMAL_RECIPE_TEXT_GIT',17 'MINIMAL_RECIPE_TEXT_GIT',
18 'RecipeBranchType',
18 ]19 ]
1920
2021
21from textwrap import dedent22from textwrap import dedent
2223
24from lazr.enum import (
25 EnumeratedType,
26 Item,
27 )
23from lazr.lifecycle.snapshot import doNotSnapshot28from lazr.lifecycle.snapshot import doNotSnapshot
24from lazr.restful.declarations import (29from lazr.restful.declarations import (
25 call_with,30 call_with,
@@ -105,13 +110,23 @@
105 """An iterator of the branches referenced by this recipe."""110 """An iterator of the branches referenced by this recipe."""
106111
107112
113class RecipeBranchType(EnumeratedType):
114 """The revision control system used for a recipe."""
115
116 BZR = Item("Bazaar")
117
118 GIT = Item("Git")
119
120
108class IRecipeBranchSource(Interface):121class IRecipeBranchSource(Interface):
109122
110 def getParsedRecipe(recipe_text):123 def getParsedRecipe(recipe_text):
111 """Parse recipe text into recipe data.124 """Parse recipe text into recipe data.
112125
113 :param recipe_text: Recipe text as a string.126 :param recipe_text: Recipe text as a string.
114 :return: a `RecipeBranch` representing the recipe.127 :return: a tuple of a `RecipeBranch` representing the recipe and a
128 `RecipeBranchType` indicating the revision control system to be
129 used for the recipe.
115 """130 """
116131
117132
118133
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2018-05-09 09:23:36 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2018-07-23 19:40:00 +0000
@@ -184,8 +184,9 @@
184 owner_ids, need_validity=True))184 owner_ids, need_validity=True))
185185
186 def setRecipeText(self, recipe_text):186 def setRecipeText(self, recipe_text):
187 parsed = getUtility(IRecipeBranchSource).getParsedRecipe(recipe_text)187 parsed, recipe_branch_type = (
188 self._recipe_data.setRecipe(parsed)188 getUtility(IRecipeBranchSource).getParsedRecipe(recipe_text))
189 self._recipe_data.setRecipe(parsed, recipe_branch_type)
189190
190 def getRecipeText(self, validate=False):191 def getRecipeText(self, validate=False):
191 """See `ISourcePackageRecipe`."""192 """See `ISourcePackageRecipe`."""
@@ -215,9 +216,9 @@
215 """See `ISourcePackageRecipeSource.new`."""216 """See `ISourcePackageRecipeSource.new`."""
216 store = IMasterStore(SourcePackageRecipe)217 store = IMasterStore(SourcePackageRecipe)
217 sprecipe = SourcePackageRecipe()218 sprecipe = SourcePackageRecipe()
218 builder_recipe = getUtility(IRecipeBranchSource).getParsedRecipe(219 builder_recipe, recipe_branch_type = (
219 recipe)220 getUtility(IRecipeBranchSource).getParsedRecipe(recipe))
220 SourcePackageRecipeData(builder_recipe, sprecipe)221 SourcePackageRecipeData(builder_recipe, recipe_branch_type, sprecipe)
221 sprecipe.registrant = registrant222 sprecipe.registrant = registrant
222 sprecipe.owner = owner223 sprecipe.owner = owner
223 sprecipe.name = name224 sprecipe.name = name
224225
=== modified file 'lib/lp/code/model/sourcepackagerecipebuild.py'
--- lib/lp/code/model/sourcepackagerecipebuild.py 2016-08-12 12:56:41 +0000
+++ lib/lp/code/model/sourcepackagerecipebuild.py 2018-07-23 19:40:00 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010-2016 Canonical Ltd. This software is licensed under the1# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Implementation code for source package builds."""4"""Implementation code for source package builds."""
@@ -164,8 +164,9 @@
164 getUtility(ISourcePackageRecipeDataSource).createManifestFromText(164 getUtility(ISourcePackageRecipeDataSource).createManifestFromText(
165 text, self)165 text, self)
166 else:166 else:
167 self.manifest.setRecipe(167 parsed, recipe_branch_type = (
168 getUtility(IRecipeBranchSource).getParsedRecipe(text))168 getUtility(IRecipeBranchSource).getParsedRecipe(text))
169 self.manifest.setRecipe(parsed, recipe_branch_type)
169170
170 def getManifestText(self):171 def getManifestText(self):
171 if self.manifest is None:172 if self.manifest is None:
172173
=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
--- lib/lp/code/model/sourcepackagerecipedata.py 2016-02-06 02:20:04 +0000
+++ lib/lp/code/model/sourcepackagerecipedata.py 2018-07-23 19:40:00 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Implementation of the recipe storage.4"""Implementation of the recipe storage.
@@ -61,6 +61,7 @@
61 IRecipeBranchSource,61 IRecipeBranchSource,
62 ISourcePackageRecipeData,62 ISourcePackageRecipeData,
63 ISourcePackageRecipeDataSource,63 ISourcePackageRecipeDataSource,
64 RecipeBranchType,
64 )65 )
65from lp.code.model.branch import Branch66from lp.code.model.branch import Branch
66from lp.code.model.gitrepository import GitRepository67from lp.code.model.gitrepository import GitRepository
@@ -239,10 +240,14 @@
239 # formats are mostly compatible, the header line must say240 # formats are mostly compatible, the header line must say
240 # "bzr-builder" even though git-build-recipe also supports its own241 # "bzr-builder" even though git-build-recipe also supports its own
241 # name there.242 # name there.
242 recipe_text = re.sub(243 recipe_text, git_substitutions = re.subn(
243 r"^(#\s*)git-build-recipe", r"\1bzr-builder", recipe_text)244 r"^(#\s*)git-build-recipe", r"\1bzr-builder", recipe_text)
245 recipe_branch_type = (
246 RecipeBranchType.GIT if git_substitutions
247 else RecipeBranchType.BZR)
244 parser = RecipeParser(recipe_text)248 parser = RecipeParser(recipe_text)
245 return parser.parse(permitted_instructions=SAFE_INSTRUCTIONS)249 recipe_branch = parser.parse(permitted_instructions=SAFE_INSTRUCTIONS)
250 return recipe_branch, recipe_branch_type
246251
247 @staticmethod252 @staticmethod
248 def findRecipes(branch_or_repository, revspecs=None):253 def findRecipes(branch_or_repository, revspecs=None):
@@ -306,9 +311,10 @@
306 @classmethod311 @classmethod
307 def createManifestFromText(cls, text, sourcepackage_recipe_build):312 def createManifestFromText(cls, text, sourcepackage_recipe_build):
308 """See `ISourcePackageRecipeDataSource`."""313 """See `ISourcePackageRecipeDataSource`."""
309 parsed = cls.getParsedRecipe(text)314 parsed, recipe_branch_type = cls.getParsedRecipe(text)
310 return cls(315 return cls(
311 parsed, sourcepackage_recipe_build=sourcepackage_recipe_build)316 parsed, recipe_branch_type,
317 sourcepackage_recipe_build=sourcepackage_recipe_build)
312318
313 def getRecipe(self):319 def getRecipe(self):
314 """The BaseRecipeBranch version of the recipe."""320 """The BaseRecipeBranch version of the recipe."""
@@ -388,26 +394,26 @@
388 instruction.recipe_branch, insn, branch_map, line_number)394 instruction.recipe_branch, insn, branch_map, line_number)
389 return line_number395 return line_number
390396
391 def setRecipe(self, builder_recipe):397 def setRecipe(self, builder_recipe, recipe_branch_type):
392 """Convert the BaseRecipeBranch `builder_recipe` to the db form."""398 """Convert the BaseRecipeBranch `builder_recipe` to the db form."""
393 clear_property_cache(self)399 clear_property_cache(self)
394 if builder_recipe.format > MAX_RECIPE_FORMAT:400 if builder_recipe.format > MAX_RECIPE_FORMAT:
395 raise TooNewRecipeFormat(builder_recipe.format, MAX_RECIPE_FORMAT)401 raise TooNewRecipeFormat(builder_recipe.format, MAX_RECIPE_FORMAT)
396 base = getUtility(IBranchLookup).getByUrl(builder_recipe.url)402 if recipe_branch_type == RecipeBranchType.BZR:
397 if base is None:403 base = getUtility(IBranchLookup).getByUrl(builder_recipe.url)
404 if base is None:
405 raise NoSuchBranch(builder_recipe.url)
406 elif base.private:
407 raise PrivateBranchRecipe(base)
408 elif recipe_branch_type == RecipeBranchType.GIT:
398 base = getUtility(IGitLookup).getByUrl(builder_recipe.url)409 base = getUtility(IGitLookup).getByUrl(builder_recipe.url)
399 if base is None:410 if base is None:
400 # If possible, try to raise an exception consistent with411 raise NoSuchGitRepository(builder_recipe.url)
401 # whether the current recipe is Bazaar-based or Git-based,
402 # so that error messages make more sense.
403 if self.base_git_repository is not None:
404 raise NoSuchGitRepository(builder_recipe.url)
405 else:
406 raise NoSuchBranch(builder_recipe.url)
407 elif base.private:412 elif base.private:
408 raise PrivateGitRepositoryRecipe(base)413 raise PrivateGitRepositoryRecipe(base)
409 elif base.private:414 else:
410 raise PrivateBranchRecipe(base)415 raise AssertionError(
416 'Unknown recipe_branch_type: %r' % recipe_branch_type)
411 branch_map = self._scanInstructions(base, builder_recipe)417 branch_map = self._scanInstructions(base, builder_recipe)
412 # If this object hasn't been added to a store yet, there can't be any418 # If this object hasn't been added to a store yet, there can't be any
413 # instructions linking to us yet.419 # instructions linking to us yet.
@@ -426,12 +432,12 @@
426 self.deb_version_template = unicode(builder_recipe.deb_version)432 self.deb_version_template = unicode(builder_recipe.deb_version)
427 self.recipe_format = unicode(builder_recipe.format)433 self.recipe_format = unicode(builder_recipe.format)
428434
429 def __init__(self, recipe, sourcepackage_recipe=None,435 def __init__(self, recipe, recipe_branch_type, sourcepackage_recipe=None,
430 sourcepackage_recipe_build=None):436 sourcepackage_recipe_build=None):
431 """Initialize from the bzr-builder recipe and link it to a db recipe.437 """Initialize from the bzr-builder recipe and link it to a db recipe.
432 """438 """
433 super(SourcePackageRecipeData, self).__init__()439 super(SourcePackageRecipeData, self).__init__()
434 self.setRecipe(recipe)440 self.setRecipe(recipe, recipe_branch_type)
435 self.sourcepackage_recipe = sourcepackage_recipe441 self.sourcepackage_recipe = sourcepackage_recipe
436 self.sourcepackage_recipe_build = sourcepackage_recipe_build442 self.sourcepackage_recipe_build = sourcepackage_recipe_build
437443
438444
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2018-03-02 01:11:48 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2018-07-23 19:40:00 +0000
@@ -37,6 +37,8 @@
37 PrivateGitRepositoryRecipe,37 PrivateGitRepositoryRecipe,
38 TooNewRecipeFormat,38 TooNewRecipeFormat,
39 )39 )
40from lp.code.interfaces.gitrepository import IGitRepositorySet
41from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
40from lp.code.interfaces.sourcepackagerecipe import (42from lp.code.interfaces.sourcepackagerecipe import (
41 ISourcePackageRecipe,43 ISourcePackageRecipe,
42 ISourcePackageRecipeSource,44 ISourcePackageRecipeSource,
@@ -1098,12 +1100,47 @@
10981100
1099class TestRecipeBranchRoundTrippingBzr(1101class TestRecipeBranchRoundTrippingBzr(
1100 TestRecipeBranchRoundTrippingMixin, BzrMixin, TestCaseWithFactory):1102 TestRecipeBranchRoundTrippingMixin, BzrMixin, TestCaseWithFactory):
1101 pass1103
1104 def test_builds_recipe_with_ambiguous_git_repository(self):
1105 # Arrange for Bazaar and Git prefixes to match.
1106 self.pushConfig('codehosting', bzr_lp_prefix='lp:', lp_url_hosts='')
1107 project = self.base_branch.product
1108 repository = self.factory.makeGitRepository(target=project)
1109 with person_logged_in(project.owner):
1110 ICanHasLinkedBranch(project).setBranch(self.base_branch)
1111 getUtility(IGitRepositorySet).setDefaultRepository(
1112 project, repository)
1113 clear_property_cache(self.base_branch)
1114 recipe_text = '''\
1115 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1116 %(base)s
1117 ''' % {'recipe_id': self.recipe_id, 'base': self.base_branch.identity}
1118 recipe = self.get_recipe(recipe_text)
1119 self.assertEqual(self.base_branch, recipe.base_branch)
11021120
11031121
1104class TestRecipeBranchRoundTrippingGit(1122class TestRecipeBranchRoundTrippingGit(
1105 TestRecipeBranchRoundTrippingMixin, GitMixin, TestCaseWithFactory):1123 TestRecipeBranchRoundTrippingMixin, GitMixin, TestCaseWithFactory):
1106 pass1124
1125 def test_builds_recipe_with_ambiguous_bzr_branch(self):
1126 # Arrange for Bazaar and Git prefixes to match.
1127 self.pushConfig('codehosting', bzr_lp_prefix='lp:', lp_url_hosts='')
1128 project = self.base_branch.target
1129 branch = self.factory.makeBranch(product=project)
1130 with person_logged_in(project.owner):
1131 ICanHasLinkedBranch(project).setBranch(branch)
1132 getUtility(IGitRepositorySet).setDefaultRepository(
1133 project, self.base_branch.repository)
1134 recipe_text = '''\
1135 # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
1136 %(base)s
1137 ''' % {
1138 'recipe_id': self.recipe_id,
1139 'base': self.base_branch.repository.identity,
1140 }
1141 recipe = self.get_recipe(recipe_text)
1142 self.assertEqual(
1143 self.base_branch.repository, recipe.base_git_repository)
11071144
11081145
1109class RecipeDateLastModified(TestCaseWithFactory):1146class RecipeDateLastModified(TestCaseWithFactory):