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
1=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
2--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2018-01-02 16:10:26 +0000
3+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2018-07-23 19:40:00 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2010-2017 Canonical Ltd. This software is licensed under the
6+# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Tests for the source package recipe view classes and templates."""
10@@ -551,12 +551,9 @@
11 # branch location, they should get an error.
12 browser = self.createRecipe(
13 self.minimal_recipe_text.splitlines()[0] + '\nfoo\n')
14- # This page doesn't know whether the user was aiming for a Bazaar
15- # branch or a Git repository; the error message always says
16- # "branch".
17 self.assertEqual(
18 get_feedback_messages(browser.contents)[1],
19- 'foo is not a branch on Launchpad.')
20+ 'foo %s' % self.no_such_object_message)
21
22 def test_create_recipe_bad_instruction_branch(self):
23 # If a user tries to create source package recipe with a bad
24
25=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
26--- lib/lp/code/interfaces/sourcepackagerecipe.py 2017-06-16 10:02:47 +0000
27+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2018-07-23 19:40:00 +0000
28@@ -1,4 +1,4 @@
29-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
30+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
31 # GNU Affero General Public License version 3 (see the file LICENSE).
32
33 """Interface of the `SourcePackageRecipe` content type."""
34@@ -15,11 +15,16 @@
35 'ISourcePackageRecipeSource',
36 'MINIMAL_RECIPE_TEXT_BZR',
37 'MINIMAL_RECIPE_TEXT_GIT',
38+ 'RecipeBranchType',
39 ]
40
41
42 from textwrap import dedent
43
44+from lazr.enum import (
45+ EnumeratedType,
46+ Item,
47+ )
48 from lazr.lifecycle.snapshot import doNotSnapshot
49 from lazr.restful.declarations import (
50 call_with,
51@@ -105,13 +110,23 @@
52 """An iterator of the branches referenced by this recipe."""
53
54
55+class RecipeBranchType(EnumeratedType):
56+ """The revision control system used for a recipe."""
57+
58+ BZR = Item("Bazaar")
59+
60+ GIT = Item("Git")
61+
62+
63 class IRecipeBranchSource(Interface):
64
65 def getParsedRecipe(recipe_text):
66 """Parse recipe text into recipe data.
67
68 :param recipe_text: Recipe text as a string.
69- :return: a `RecipeBranch` representing the recipe.
70+ :return: a tuple of a `RecipeBranch` representing the recipe and a
71+ `RecipeBranchType` indicating the revision control system to be
72+ used for the recipe.
73 """
74
75
76
77=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
78--- lib/lp/code/model/sourcepackagerecipe.py 2018-05-09 09:23:36 +0000
79+++ lib/lp/code/model/sourcepackagerecipe.py 2018-07-23 19:40:00 +0000
80@@ -184,8 +184,9 @@
81 owner_ids, need_validity=True))
82
83 def setRecipeText(self, recipe_text):
84- parsed = getUtility(IRecipeBranchSource).getParsedRecipe(recipe_text)
85- self._recipe_data.setRecipe(parsed)
86+ parsed, recipe_branch_type = (
87+ getUtility(IRecipeBranchSource).getParsedRecipe(recipe_text))
88+ self._recipe_data.setRecipe(parsed, recipe_branch_type)
89
90 def getRecipeText(self, validate=False):
91 """See `ISourcePackageRecipe`."""
92@@ -215,9 +216,9 @@
93 """See `ISourcePackageRecipeSource.new`."""
94 store = IMasterStore(SourcePackageRecipe)
95 sprecipe = SourcePackageRecipe()
96- builder_recipe = getUtility(IRecipeBranchSource).getParsedRecipe(
97- recipe)
98- SourcePackageRecipeData(builder_recipe, sprecipe)
99+ builder_recipe, recipe_branch_type = (
100+ getUtility(IRecipeBranchSource).getParsedRecipe(recipe))
101+ SourcePackageRecipeData(builder_recipe, recipe_branch_type, sprecipe)
102 sprecipe.registrant = registrant
103 sprecipe.owner = owner
104 sprecipe.name = name
105
106=== modified file 'lib/lp/code/model/sourcepackagerecipebuild.py'
107--- lib/lp/code/model/sourcepackagerecipebuild.py 2016-08-12 12:56:41 +0000
108+++ lib/lp/code/model/sourcepackagerecipebuild.py 2018-07-23 19:40:00 +0000
109@@ -1,4 +1,4 @@
110-# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
111+# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
112 # GNU Affero General Public License version 3 (see the file LICENSE).
113
114 """Implementation code for source package builds."""
115@@ -164,8 +164,9 @@
116 getUtility(ISourcePackageRecipeDataSource).createManifestFromText(
117 text, self)
118 else:
119- self.manifest.setRecipe(
120+ parsed, recipe_branch_type = (
121 getUtility(IRecipeBranchSource).getParsedRecipe(text))
122+ self.manifest.setRecipe(parsed, recipe_branch_type)
123
124 def getManifestText(self):
125 if self.manifest is None:
126
127=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
128--- lib/lp/code/model/sourcepackagerecipedata.py 2016-02-06 02:20:04 +0000
129+++ lib/lp/code/model/sourcepackagerecipedata.py 2018-07-23 19:40:00 +0000
130@@ -1,4 +1,4 @@
131-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
132+# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
133 # GNU Affero General Public License version 3 (see the file LICENSE).
134
135 """Implementation of the recipe storage.
136@@ -61,6 +61,7 @@
137 IRecipeBranchSource,
138 ISourcePackageRecipeData,
139 ISourcePackageRecipeDataSource,
140+ RecipeBranchType,
141 )
142 from lp.code.model.branch import Branch
143 from lp.code.model.gitrepository import GitRepository
144@@ -239,10 +240,14 @@
145 # formats are mostly compatible, the header line must say
146 # "bzr-builder" even though git-build-recipe also supports its own
147 # name there.
148- recipe_text = re.sub(
149+ recipe_text, git_substitutions = re.subn(
150 r"^(#\s*)git-build-recipe", r"\1bzr-builder", recipe_text)
151+ recipe_branch_type = (
152+ RecipeBranchType.GIT if git_substitutions
153+ else RecipeBranchType.BZR)
154 parser = RecipeParser(recipe_text)
155- return parser.parse(permitted_instructions=SAFE_INSTRUCTIONS)
156+ recipe_branch = parser.parse(permitted_instructions=SAFE_INSTRUCTIONS)
157+ return recipe_branch, recipe_branch_type
158
159 @staticmethod
160 def findRecipes(branch_or_repository, revspecs=None):
161@@ -306,9 +311,10 @@
162 @classmethod
163 def createManifestFromText(cls, text, sourcepackage_recipe_build):
164 """See `ISourcePackageRecipeDataSource`."""
165- parsed = cls.getParsedRecipe(text)
166+ parsed, recipe_branch_type = cls.getParsedRecipe(text)
167 return cls(
168- parsed, sourcepackage_recipe_build=sourcepackage_recipe_build)
169+ parsed, recipe_branch_type,
170+ sourcepackage_recipe_build=sourcepackage_recipe_build)
171
172 def getRecipe(self):
173 """The BaseRecipeBranch version of the recipe."""
174@@ -388,26 +394,26 @@
175 instruction.recipe_branch, insn, branch_map, line_number)
176 return line_number
177
178- def setRecipe(self, builder_recipe):
179+ def setRecipe(self, builder_recipe, recipe_branch_type):
180 """Convert the BaseRecipeBranch `builder_recipe` to the db form."""
181 clear_property_cache(self)
182 if builder_recipe.format > MAX_RECIPE_FORMAT:
183 raise TooNewRecipeFormat(builder_recipe.format, MAX_RECIPE_FORMAT)
184- base = getUtility(IBranchLookup).getByUrl(builder_recipe.url)
185- if base is None:
186+ if recipe_branch_type == RecipeBranchType.BZR:
187+ base = getUtility(IBranchLookup).getByUrl(builder_recipe.url)
188+ if base is None:
189+ raise NoSuchBranch(builder_recipe.url)
190+ elif base.private:
191+ raise PrivateBranchRecipe(base)
192+ elif recipe_branch_type == RecipeBranchType.GIT:
193 base = getUtility(IGitLookup).getByUrl(builder_recipe.url)
194 if base is None:
195- # If possible, try to raise an exception consistent with
196- # whether the current recipe is Bazaar-based or Git-based,
197- # so that error messages make more sense.
198- if self.base_git_repository is not None:
199- raise NoSuchGitRepository(builder_recipe.url)
200- else:
201- raise NoSuchBranch(builder_recipe.url)
202+ raise NoSuchGitRepository(builder_recipe.url)
203 elif base.private:
204 raise PrivateGitRepositoryRecipe(base)
205- elif base.private:
206- raise PrivateBranchRecipe(base)
207+ else:
208+ raise AssertionError(
209+ 'Unknown recipe_branch_type: %r' % recipe_branch_type)
210 branch_map = self._scanInstructions(base, builder_recipe)
211 # If this object hasn't been added to a store yet, there can't be any
212 # instructions linking to us yet.
213@@ -426,12 +432,12 @@
214 self.deb_version_template = unicode(builder_recipe.deb_version)
215 self.recipe_format = unicode(builder_recipe.format)
216
217- def __init__(self, recipe, sourcepackage_recipe=None,
218+ def __init__(self, recipe, recipe_branch_type, sourcepackage_recipe=None,
219 sourcepackage_recipe_build=None):
220 """Initialize from the bzr-builder recipe and link it to a db recipe.
221 """
222 super(SourcePackageRecipeData, self).__init__()
223- self.setRecipe(recipe)
224+ self.setRecipe(recipe, recipe_branch_type)
225 self.sourcepackage_recipe = sourcepackage_recipe
226 self.sourcepackage_recipe_build = sourcepackage_recipe_build
227
228
229=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
230--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2018-03-02 01:11:48 +0000
231+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2018-07-23 19:40:00 +0000
232@@ -37,6 +37,8 @@
233 PrivateGitRepositoryRecipe,
234 TooNewRecipeFormat,
235 )
236+from lp.code.interfaces.gitrepository import IGitRepositorySet
237+from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
238 from lp.code.interfaces.sourcepackagerecipe import (
239 ISourcePackageRecipe,
240 ISourcePackageRecipeSource,
241@@ -1098,12 +1100,47 @@
242
243 class TestRecipeBranchRoundTrippingBzr(
244 TestRecipeBranchRoundTrippingMixin, BzrMixin, TestCaseWithFactory):
245- pass
246+
247+ def test_builds_recipe_with_ambiguous_git_repository(self):
248+ # Arrange for Bazaar and Git prefixes to match.
249+ self.pushConfig('codehosting', bzr_lp_prefix='lp:', lp_url_hosts='')
250+ project = self.base_branch.product
251+ repository = self.factory.makeGitRepository(target=project)
252+ with person_logged_in(project.owner):
253+ ICanHasLinkedBranch(project).setBranch(self.base_branch)
254+ getUtility(IGitRepositorySet).setDefaultRepository(
255+ project, repository)
256+ clear_property_cache(self.base_branch)
257+ recipe_text = '''\
258+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
259+ %(base)s
260+ ''' % {'recipe_id': self.recipe_id, 'base': self.base_branch.identity}
261+ recipe = self.get_recipe(recipe_text)
262+ self.assertEqual(self.base_branch, recipe.base_branch)
263
264
265 class TestRecipeBranchRoundTrippingGit(
266 TestRecipeBranchRoundTrippingMixin, GitMixin, TestCaseWithFactory):
267- pass
268+
269+ def test_builds_recipe_with_ambiguous_bzr_branch(self):
270+ # Arrange for Bazaar and Git prefixes to match.
271+ self.pushConfig('codehosting', bzr_lp_prefix='lp:', lp_url_hosts='')
272+ project = self.base_branch.target
273+ branch = self.factory.makeBranch(product=project)
274+ with person_logged_in(project.owner):
275+ ICanHasLinkedBranch(project).setBranch(branch)
276+ getUtility(IGitRepositorySet).setDefaultRepository(
277+ project, self.base_branch.repository)
278+ recipe_text = '''\
279+ # %(recipe_id)s format 0.3 deb-version 0.1-{revno}
280+ %(base)s
281+ ''' % {
282+ 'recipe_id': self.recipe_id,
283+ 'base': self.base_branch.repository.identity,
284+ }
285+ recipe = self.get_recipe(recipe_text)
286+ self.assertEqual(
287+ self.base_branch.repository, recipe.base_git_repository)
288
289
290 class RecipeDateLastModified(TestCaseWithFactory):