← Back to team overview

launchpad-reviewers team mailing list archive

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

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/git-recipe-find into lp:launchpad with lp:~cjwatson/launchpad/git-recipe-model as a prerequisite.

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/282255

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/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:14:12 +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:14:12 +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/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:14:12 +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:14:12 +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/sourcepackagerecipedata.py'
--- lib/lp/code/model/sourcepackagerecipedata.py	2016-01-12 04:14:12 +0000
+++ lib/lp/code/model/sourcepackagerecipedata.py	2016-01-12 04:14:12 +0000
@@ -30,6 +30,7 @@
 from storm.expr import Union
 from storm.locals import (
     And,
+    In,
     Int,
     Reference,
     ReferenceSet,
@@ -244,21 +245,50 @@
         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)
                     )
             ))
         )

=== 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:14:12 +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	2016-01-12 04:14:12 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py	2016-01-12 04:14:12 +0000
@@ -1098,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_BZR % 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
@@ -1279,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/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:14:12 +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)
 


Follow ups