# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1695549262 -7200
#      Sun Sep 24 11:54:22 2023 +0200
# Node ID ff9416677544542275ac771acbdbd43ddd01647a
# Parent  14ed25522afed680513eaf71c5c7e36f1b404e84
RepositoryService: move repo creation to servicer base class

This is not exactly what was suggested in the removed todo comment,
because it contains context-based error treatment and uses repository
loading methods, but still provides clean mutualization, that could
for instance also been used in `MercurialRepositoryService`.

diff --git a/hgitaly/service/repository.py b/hgitaly/service/repository.py
--- a/hgitaly/service/repository.py
+++ b/hgitaly/service/repository.py
@@ -20,7 +20,6 @@
     archival,
     pycompat,
     scmutil,
-    hg,
     node,
 )
 from mercurial.commands import (
@@ -47,7 +46,6 @@
     iter_gitlab_branches,
 )
 from ..errors import (
-    ServiceError,
     not_implemented,
 )
 from ..gitlab_ref import (
@@ -125,9 +123,6 @@
     ApplyGitattributesResponse,
 )
 from ..stub.repository_pb2_grpc import RepositoryServiceServicer
-from ..stub.shared_pb2 import (
-    Repository,
-)
 
 
 base_logger = logging.getLogger(__name__)
@@ -145,10 +140,6 @@
 """
 
 
-class RepositoryCreationError(ServiceError):
-    """Specific exception for creation problems."""
-
-
 class RepositoryServicer(RepositoryServiceServicer, HGitalyServicer):
     """RepositoryServiceService implementation.
     """
@@ -669,14 +660,10 @@
         sub-request contains repository+data and subsequent sub-requests
         contains only data (i.e. the actual bundle sent in parts).
         """
-        def init_repo(req, context):
-            # TODO should move to hgitaly.repository
-            self.hg_init_repository(req.repository, context)
-            return self.load_repo(req.repository, context)
-
         with streaming_request_tempfile_extract(
                 request, context,
-                first_request_handler=init_repo
+                first_request_handler=lambda rq, c: self.create_and_load_repo(
+                    rq.repository, c),
         ) as (repo, tmpf):
             try:
                 unbundle(repo, tmpf.name)
@@ -722,39 +709,6 @@
             context.abort(StatusCode.INTERNAL, "remove all: %s" % exc)
         return RemoveAllResponse()
 
-    def hg_init_repository(self, repository: Repository, context):
-        """Initialize a mercurial repository from a request object.
-
-        :return: ``None``: the resulting repository has to be loaded in the
-           standard way, using its path.
-        :raises RepositoryCreationError: and updates context with error
-           code and details.
-        """
-        logger = LoggerAdapter(base_logger, context)
-        try:
-            repo_path = self.repo_disk_path(repository, context)
-        except KeyError:
-            msg = "no such storage: %r" % repository.storage_name
-            context.set_details(msg)
-            logger.error(msg)
-            context.set_code(StatusCode.INVALID_ARGUMENT)
-            raise RepositoryCreationError(repository)
-
-        if os.path.lexists(repo_path):
-            msg = ("creating repository: repository exists already")
-            context.set_details(msg)
-            context.set_code(StatusCode.ALREADY_EXISTS)
-            raise RepositoryCreationError(repository)
-
-        try:
-            logger.info("Creating repository at %r", repo_path)
-            hg.peer(self.ui, opts={}, path=repo_path, create=True)
-        except OSError as exc:
-            context.set_code(StatusCode.INTERNAL)
-            context.set_details("hg_init_repository(%r): %r" % (repo_path,
-                                                                exc))
-            raise RepositoryCreationError(repository)
-
 
 def render_git_grep_matches(buf, ref, path, enum_lines):
     """Render a slice of lines as git grep does.
diff --git a/hgitaly/servicer.py b/hgitaly/servicer.py
--- a/hgitaly/servicer.py
+++ b/hgitaly/servicer.py
@@ -20,6 +20,8 @@
 )
 from mercurial.repoview import _filteredrepotypes
 
+from .errors import ServiceError
+from .logging import LoggerAdapter
 from .workdir import working_directory
 from .stub.shared_pb2 import (
     Repository,
@@ -58,6 +60,10 @@
     return rpath
 
 
+class RepositoryCreationError(ServiceError):
+    """Specific exception for creation problems."""
+
+
 class HGitalyServicer:
     """Common features of all HGitaly services.
 
@@ -138,6 +144,14 @@
             context.abort(self.STATUS_CODE_REPO_NOT_FOUND,
                           exc_args[1])
 
+    def create_and_load_repo(self, repository: Repository, context):
+        """Create the repository and load it.
+
+        :raises RepositoryCreationError:
+        """
+        self.hg_init_repository(repository, context)
+        return self.load_repo_inner(repository, context)
+
     def load_repo_inner(self, repository: Repository, context):
         """Load the repository from storage name and relative path
 
@@ -273,3 +287,36 @@
         with working_directory(tmp / 'workdirs' / rpath, repo,
                                changeset=changeset) as wd:
             yield wd
+
+    def hg_init_repository(self, repository: Repository, context):
+        """Initialize a mercurial repository from a request object.
+
+        :return: ``None``: the resulting repository has to be loaded in the
+           standard way, using its path.
+        :raises RepositoryCreationError: and updates context with error
+           code and details.
+        """
+        corr_logger = LoggerAdapter(logger, context)
+        try:
+            repo_path = self.repo_disk_path(repository, context)
+        except KeyError:
+            msg = "no such storage: %r" % repository.storage_name
+            context.set_details(msg)
+            corr_logger.error(msg)
+            context.set_code(StatusCode.INVALID_ARGUMENT)
+            raise RepositoryCreationError(repository)
+
+        if os.path.lexists(repo_path):
+            msg = ("creating repository: repository exists already")
+            context.set_details(msg)
+            context.set_code(StatusCode.ALREADY_EXISTS)
+            raise RepositoryCreationError(repository)
+
+        try:
+            corr_logger.info("Creating repository at %r", repo_path)
+            hg.peer(self.ui, opts={}, path=repo_path, create=True)
+        except OSError as exc:
+            context.set_code(StatusCode.INTERNAL)
+            context.set_details("hg_init_repository(%r): %r" % (repo_path,
+                                                                exc))
+            raise RepositoryCreationError(repository)