← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~ilasc/launchpad:add-repack-endpoint into launchpad:master

 

Ioana Lasc has proposed merging ~ilasc/launchpad:add-repack-endpoint into launchpad:master.

Commit message:
Add endpoint for git repository repack

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~ilasc/launchpad/+git/launchpad/+merge/395148
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~ilasc/launchpad:add-repack-endpoint into launchpad:master.
diff --git a/lib/lp/code/errors.py b/lib/lp/code/errors.py
index bed7db5..69646ef 100644
--- a/lib/lp/code/errors.py
+++ b/lib/lp/code/errors.py
@@ -25,6 +25,7 @@ __all__ = [
     'CannotDeleteGitRepository',
     'CannotHaveLinkedBranch',
     'CannotModifyNonHostedGitRepository',
+    'CannotRepackRepository',
     'CannotUpgradeBranch',
     'CannotUpgradeNonHosted',
     'CodeImportAlreadyRequested',
@@ -490,6 +491,11 @@ class GitRepositoryBlobUnsupportedRemote(Exception):
             self.repository_url)
 
 
+@error_status(http_client.FORBIDDEN)
+class CannotRepackRepository(Exception):
+    """Tried to repack a repository."""
+
+
 class GitRepositoryDeletionFault(Exception):
     """Raised when there is a fault deleting a repository."""
 
diff --git a/lib/lp/code/interfaces/githosting.py b/lib/lp/code/interfaces/githosting.py
index 441e850..9d98c3f 100644
--- a/lib/lp/code/interfaces/githosting.py
+++ b/lib/lp/code/interfaces/githosting.py
@@ -148,3 +148,10 @@ class IGitHostingClient(Interface):
         :param refs: A list of tuples like (repo_path, ref_name) to be deleted.
         :param logger: An optional logger.
         """
+
+    def repackRepository(path):
+        """Repack a Git repository.
+
+        :param path: Physical path of the new repository on the hosting
+            service.
+        """
diff --git a/lib/lp/code/interfaces/gitrepository.py b/lib/lp/code/interfaces/gitrepository.py
index ed17183..8ef5043 100644
--- a/lib/lp/code/interfaces/gitrepository.py
+++ b/lib/lp/code/interfaces/gitrepository.py
@@ -1161,6 +1161,32 @@ class IGitRepositorySet(Interface):
             Projects that do not have default repositories are omitted.
         """
 
+    @call_with(user=REQUEST_USER)
+    @operation_parameters(
+        path=TextLine(title=_("Repository path"), required=True))
+    @export_read_operation()
+    @operation_for_version("devel")
+    def repackRepository(user, path):
+        """Trigger a repack repository operation.
+
+        :param path: The repository path.
+
+        Any of these forms may be used::
+
+            Unique names:
+                ~OWNER/PROJECT/+git/NAME
+                ~OWNER/DISTRO/+source/SOURCE/+git/NAME
+                ~OWNER/+git/NAME
+            Owner-target default aliases:
+                ~OWNER/PROJECT
+                ~OWNER/DISTRO/+source/SOURCE
+            Official aliases:
+                PROJECT
+                DISTRO/+source/SOURCE
+
+        Return None if no match was found.
+        """
+
 
 class IGitRepositoryDelta(Interface):
     """The quantitative changes made to a Git repository that was edited or
diff --git a/lib/lp/code/model/githosting.py b/lib/lp/code/model/githosting.py
index 74f4ec0..4aa9d7c 100644
--- a/lib/lp/code/model/githosting.py
+++ b/lib/lp/code/model/githosting.py
@@ -33,7 +33,6 @@ from lp.code.errors import (
     GitRepositoryDeletionFault,
     GitRepositoryScanFault,
     GitTargetError,
-    NoSuchGitReference,
     )
 from lp.code.interfaces.githosting import IGitHostingClient
 from lp.services.config import config
@@ -309,3 +308,8 @@ class GitHostingClient:
                 raise GitReferenceDeletionFault(
                     "Error deleting %s from repo %s: HTTP %s" %
                     (ref, path, e.response.status_code))
+
+    def repackRepository(self, path):
+        """See `IGitHostingClient`."""
+        url = "/repo/%s/repack" % path
+        self._post(url)
diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py
index fb5e703..843d653 100644
--- a/lib/lp/code/model/gitrepository.py
+++ b/lib/lp/code/model/gitrepository.py
@@ -102,6 +102,7 @@ from lp.code.enums import (
 from lp.code.errors import (
     CannotDeleteGitRepository,
     CannotModifyNonHostedGitRepository,
+    CannotRepackRepository,
     GitDefaultConflict,
     GitTargetError,
     NoSuchGitReference,
@@ -172,6 +173,7 @@ from lp.registry.model.accesspolicy import (
     reconcile_access_for_artifact,
     )
 from lp.registry.model.person import Person
+from lp.registry.model.personroles import PersonRoles
 from lp.registry.model.teammembership import TeamParticipation
 from lp.services.config import config
 from lp.services.database import bulk
@@ -1925,6 +1927,18 @@ class GitRepositorySet:
         """See `IGitRepositorySet`."""
         return []
 
+    def repackRepository(self, user, path):
+        roles = PersonRoles(user)
+        if roles.in_admin or roles.in_registry_experts:
+            repository, extra_path = getUtility(IGitLookup).getByPath(path)
+            if repository is None:
+                return None
+            repository_path = repository.getInternalPath()
+            getUtility(IGitHostingClient).repackRepository(repository_path)
+        else:
+            raise CannotRepackRepository(
+                "Only admins and registry experts can repack repositories")
+
     @staticmethod
     def preloadDefaultRepositoriesForProjects(projects):
         repositories = bulk.load_referencing(
diff --git a/lib/lp/code/model/tests/test_gitrepository.py b/lib/lp/code/model/tests/test_gitrepository.py
index b83f9da..99a1b81 100644
--- a/lib/lp/code/model/tests/test_gitrepository.py
+++ b/lib/lp/code/model/tests/test_gitrepository.py
@@ -139,7 +139,10 @@ from lp.registry.interfaces.accesspolicy import (
     IAccessPolicyArtifactSource,
     IAccessPolicySource,
     )
-from lp.registry.interfaces.person import IPerson
+from lp.registry.interfaces.person import (
+    IPerson,
+    IPersonSet,
+    )
 from lp.registry.interfaces.persondistributionsourcepackage import (
     IPersonDistributionSourcePackageFactory,
     )
@@ -195,6 +198,7 @@ from lp.testing.pages import (
     LaunchpadWebServiceCaller,
     webservice_for_person,
     )
+from lp.testing.sampledata import ADMIN_EMAIL
 from lp.xmlrpc import faults
 from lp.xmlrpc.interfaces import IPrivateApplication
 
@@ -3824,6 +3828,46 @@ class TestGitRepositoryWebservice(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer
 
+    def test_repackRepository(self):
+        hosting_fixture = self.useFixture(GitHostingFixture())
+        owner_db = self.factory.makePerson()
+        repository_db = self.factory.makeGitRepository(
+            owner=owner_db, name="repository")
+        repo_path = repository_db.shortened_path
+        webservice_user = getUtility(IPersonSet).find(ADMIN_EMAIL).any()
+        admin_url = api_url(webservice_user)
+        webservice = webservice_for_person(
+            webservice_user, permission=OAuthPermission.WRITE_PUBLIC)
+        webservice.default_api_version = "devel"
+        response = webservice.named_get(
+            "/+git", "repackRepository", user=admin_url, path=repo_path)
+        self.assertEqual(200, response.status)
+        self.assertEqual(1, hosting_fixture.repackRepository.call_count)
+
+        with person_logged_in(ANONYMOUS):
+            user_url = api_url(owner_db)
+        webservice = webservice_for_person(
+            owner_db, permission=OAuthPermission.WRITE_PUBLIC)
+        webservice.default_api_version = "devel"
+        response = webservice.named_get(
+            "/+git", "repackRepository", user=user_url, path=repo_path)
+        self.assertEqual(403, response.status)
+        self.assertEqual(1, hosting_fixture.repackRepository.call_count)
+
+        logout()
+        with admin_logged_in():
+            person = self.factory.makePerson()
+            reg_expert = getUtility(IPersonSet).getByName('registry')
+            reg_expert.addMember(person, reg_expert)
+            reg_expert_url = api_url(reg_expert)
+        webservice = webservice_for_person(
+            webservice_user, permission=OAuthPermission.WRITE_PUBLIC)
+        webservice.default_api_version = "devel"
+        response = webservice.named_get(
+            "/+git", "repackRepository", user=reg_expert_url, path=repo_path)
+        self.assertEqual(200, response.status)
+        self.assertEqual(2, hosting_fixture.repackRepository.call_count)
+
     def test_urls(self):
         owner_db = self.factory.makePerson(name="person")
         project_db = self.factory.makeProduct(name="project")
diff --git a/lib/lp/code/tests/helpers.py b/lib/lp/code/tests/helpers.py
index fae251a..a336a5c 100644
--- a/lib/lp/code/tests/helpers.py
+++ b/lib/lp/code/tests/helpers.py
@@ -329,6 +329,7 @@ class GitHostingFixture(fixtures.Fixture):
         self.getBlob = FakeMethod(result=blob)
         self.delete = FakeMethod()
         self.disable_memcache = disable_memcache
+        self.repackRepository = FakeMethod()
 
     def _setUp(self):
         self.useFixture(ZopeUtilityFixture(self, IGitHostingClient))