← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~tushar5526/launchpad:add-support-for-git-path-and-git-repository-url into launchpad:master

 

Tushar Gupta has proposed merging ~tushar5526/launchpad:add-support-for-git-path-and-git-repository-url into launchpad:master.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~tushar5526/launchpad/+git/launchpad/+merge/486448
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~tushar5526/launchpad:add-support-for-git-path-and-git-repository-url into launchpad:master.
diff --git a/lib/lp/charms/interfaces/charmrecipe.py b/lib/lp/charms/interfaces/charmrecipe.py
index 77dcf02..e947fd7 100644
--- a/lib/lp/charms/interfaces/charmrecipe.py
+++ b/lib/lp/charms/interfaces/charmrecipe.py
@@ -83,6 +83,7 @@ from lp.services.fields import (
     PersonChoice,
     PublicPersonChoice,
     SnapBuildChannelsField,
+    URIField,
 )
 from lp.services.webhooks.interfaces import IWebhookTarget
 from lp.snappy.validators.channels import channels_validator
@@ -622,22 +623,46 @@ class ICharmRecipeEditableAttributes(Interface):
         )
     )
 
-    git_repository = ReferenceChoice(
-        title=_("Git repository"),
-        schema=IGitRepository,
-        vocabulary="GitRepository",
-        required=False,
-        readonly=True,
-        description=_(
-            "A Git repository with a branch containing a charm recipe."
-        ),
+    git_repository = exported(
+        ReferenceChoice(
+            title=_("Git repository"),
+            schema=IGitRepository,
+            vocabulary="GitRepository",
+            required=False,
+            readonly=True,
+            description=_(
+                "A Git repository with a branch containing a charm recipe."
+            ),
+        )
     )
 
-    git_path = TextLine(
-        title=_("Git branch path"),
-        required=False,
-        readonly=True,
-        description=_("The path of the Git branch containing a charm recipe."),
+    git_path = exported(
+        TextLine(
+            title=_("Git branch path"),
+            required=False,
+            readonly=True,
+            description=_(
+                "The path of the Git branch containing a charm recipe."
+            ),
+        )
+    )
+
+    git_repository_url = exported(
+        URIField(
+            title=_("Git repository URL"),
+            required=False,
+            readonly=True,
+            description=_(
+                "The URL of a Git repository with a branch containing a "
+                "charmcraft.yaml at the top level."
+            ),
+            allowed_schemes=["git", "http", "https"],
+            allow_userinfo=True,
+            allow_port=True,
+            allow_query=False,
+            allow_fragment=False,
+            trailing_slash=False,
+        )
     )
 
     git_ref = exported(
@@ -830,6 +855,9 @@ class ICharmRecipeSet(Interface):
             "project",
             "name",
             "description",
+            "git_repository",
+            "git_repository_url",
+            "git_path",
             "git_ref",
             "build_path",
             "auto_build",
@@ -846,6 +874,9 @@ class ICharmRecipeSet(Interface):
         project,
         name,
         description=None,
+        git_repository=None,
+        git_repository_url=None,
+        git_path=None,
         git_ref=None,
         build_path=None,
         require_virtualized=True,
diff --git a/lib/lp/charms/model/charmrecipe.py b/lib/lp/charms/model/charmrecipe.py
index 82ea9c3..dfea776 100644
--- a/lib/lp/charms/model/charmrecipe.py
+++ b/lib/lp/charms/model/charmrecipe.py
@@ -43,6 +43,7 @@ from lp.app.enums import (
     PUBLIC_INFORMATION_TYPES,
     InformationType,
 )
+from lp.app.errors import IncompatibleArguments
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.buildmaster.builderproxy import FetchServicePolicy
 from lp.buildmaster.enums import BuildStatus
@@ -88,7 +89,7 @@ from lp.code.interfaces.gitcollection import (
     IAllGitRepositories,
     IGitCollection,
 )
-from lp.code.interfaces.gitref import IGitRef
+from lp.code.interfaces.gitref import IGitRef, IGitRefRemoteSet
 from lp.code.interfaces.gitrepository import IGitRepository
 from lp.code.model.gitcollection import GenericGitCollection
 from lp.code.model.gitref import GitRef
@@ -284,6 +285,8 @@ class CharmRecipe(StormBase, WebhookTargetMixin):
     )
     git_repository = Reference(git_repository_id, "GitRepository.id")
 
+    git_repository_url = Unicode(name="git_repository_url", allow_none=True)
+
     git_path = Unicode(name="git_path", allow_none=True)
 
     build_path = Unicode(name="build_path", allow_none=True)
@@ -392,6 +395,10 @@ class CharmRecipe(StormBase, WebhookTargetMixin):
     def _git_ref(self):
         if self.git_repository is not None:
             return self.git_repository.getRefByPath(self.git_path)
+        elif self.git_repository_url is not None:
+            return getUtility(IGitRefRemoteSet).new(
+                self.git_repository_url, self.git_path
+            )
         else:
             return None
 
@@ -405,9 +412,11 @@ class CharmRecipe(StormBase, WebhookTargetMixin):
         """See `ICharmRecipe`."""
         if value is not None:
             self.git_repository = value.repository
+            self.git_repository_url = value.repository_url
             self.git_path = value.path
         else:
             self.git_repository = None
+            self.git_repository_url = None
             self.git_path = None
         get_property_cache(self)._git_ref = value
 
@@ -960,6 +969,9 @@ class CharmRecipeSet:
         project,
         name,
         description=None,
+        git_repository=None,
+        git_repository_url=None,
+        git_path=None,
         git_ref=None,
         build_path=None,
         require_virtualized=True,
@@ -986,6 +998,33 @@ class CharmRecipeSet:
                     "%s cannot create charm recipes owned by %s."
                     % (registrant.displayname, owner.displayname)
                 )
+        if (
+            sum(
+                [
+                    git_repository is not None,
+                    git_repository_url is not None,
+                    git_ref is not None,
+                ]
+            )
+            > 1
+        ):
+            raise IncompatibleArguments(
+                "You cannot specify more than one of 'git_repository', "
+                "'git_repository_url', and 'git_ref'."
+            )
+        if (git_repository is None and git_repository_url is None) != (
+            git_path is None
+        ):
+            raise IncompatibleArguments(
+                "You must specify both or neither of "
+                "'git_repository'/'git_repository_url' and 'git_path'."
+            )
+        if git_repository is not None:
+            git_ref = git_repository.getRefByPath(git_path)
+        elif git_repository_url is not None:
+            git_ref = getUtility(IGitRefRemoteSet).new(
+                git_repository_url, git_path
+            )
 
         if git_ref is None:
             raise NoSourceForCharmRecipe
@@ -1073,7 +1112,12 @@ class CharmRecipeSet:
 
         git_collection = removeSecurityProxy(getUtility(IAllGitRepositories))
         git_recipes = _getRecipes(git_collection)
-        return git_recipes
+        git_url_recipes = IStore(CharmRecipe).find(
+            CharmRecipe,
+            CharmRecipe.owner == person,
+            CharmRecipe.git_repository_url != None,
+        )
+        return git_recipes.union(git_url_recipes)
 
     def findByProject(self, project, visible_by_user=None):
         """See `ICharmRecipeSet`."""
diff --git a/lib/lp/charms/tests/test_charmrecipe.py b/lib/lp/charms/tests/test_charmrecipe.py
index e837e4b..402d890 100644
--- a/lib/lp/charms/tests/test_charmrecipe.py
+++ b/lib/lp/charms/tests/test_charmrecipe.py
@@ -1706,6 +1706,17 @@ class TestCharmRecipeSet(TestCaseWithFactory):
             FetchServicePolicy.STRICT, recipe.fetch_service_policy
         )
 
+    def test_creation_git_url(self):
+        # A charm recipe can be backed directly by a URL for an external Git
+        # repository, rather than a Git repository hosted in Launchpad.
+        ref = self.factory.makeGitRefRemote()
+        components = self.makeCharmRecipeComponents(git_ref=ref)
+        charm_recipe = getUtility(ICharmRecipeSet).new(**components)
+        self.assertIsNone(charm_recipe.git_repository)
+        self.assertEqual(ref.repository_url, charm_recipe.git_repository_url)
+        self.assertEqual(ref.path, charm_recipe.git_path)
+        self.assertEqual(ref, charm_recipe.git_ref)
+
     def test_creation_no_source(self):
         # Attempting to create a charm recipe without a Git repository
         # fails.

Follow ups