2
0
Jelmer Vernooij 3 долоо хоног өмнө
parent
commit
d98368f5c9

+ 17 - 11
dulwich/aiohttp/server.py

@@ -43,6 +43,11 @@ from ..web import NO_CACHE_HEADERS, cache_forever_headers
 
 logger = log_utils.getLogger(__name__)
 
+# Application keys for type-safe access to app state
+REPO_KEY = web.AppKey("repo", Repo)
+HANDLERS_KEY = web.AppKey("handlers", dict)
+DUMB_KEY = web.AppKey("dumb", bool)
+
 
 async def send_file(
     req: web.Request, f: BinaryIO | None, headers: dict[str, str]
@@ -82,7 +87,7 @@ async def get_loose_object(request: web.Request) -> web.Response:
     """
     sha = (request.match_info["dir"] + request.match_info["file"]).encode("ascii")
     logger.info("Sending loose object %s", sha)
-    object_store = request.app["repo"].object_store
+    object_store = request.app[REPO_KEY].object_store
     if not object_store.contains_loose(sha):
         raise web.HTTPNotFound(text="Object not found")
     try:
@@ -105,7 +110,7 @@ async def get_text_file(request: web.Request) -> web.StreamResponse:
     headers.update(NO_CACHE_HEADERS)
     path = request.match_info["file"]
     logger.info("Sending plain text file %s", path)
-    repo = request.app["repo"]
+    repo = request.app[REPO_KEY]
     return await send_file(request, repo.get_named_file(path), headers)
 
 
@@ -169,8 +174,8 @@ async def get_info_refs(request: web.Request) -> web.StreamResponse | web.Respon
       request: aiohttp request object
     Returns: Response with refs information
     """
-    repo = request.app["repo"]
-    return await refs_request(repo, request, request.app["handlers"])
+    repo = request.app[REPO_KEY]
+    return await refs_request(repo, request, request.app[HANDLERS_KEY])
 
 
 async def get_info_packs(request: web.Request) -> web.Response:
@@ -184,7 +189,8 @@ async def get_info_packs(request: web.Request) -> web.Response:
     headers.update(NO_CACHE_HEADERS)
     logger.info("Emulating dumb info/packs")
     return web.Response(
-        body=b"".join(generate_objects_info_packs(request.app["repo"])), headers=headers
+        body=b"".join(generate_objects_info_packs(request.app[REPO_KEY])),
+        headers=headers,
     )
 
 
@@ -202,7 +208,7 @@ async def get_pack_file(request: web.Request) -> web.StreamResponse:
     logger.info("Sending pack file %s", path)
     return await send_file(
         request,
-        request.app["repo"].get_named_file(path),
+        request.app[REPO_KEY].get_named_file(path),
         headers=headers,
     )
 
@@ -281,9 +287,9 @@ async def handle_service_request(request: web.Request) -> web.StreamResponse:
       request: aiohttp request object
     Returns: Response with service result
     """
-    repo = request.app["repo"]
+    repo = request.app[REPO_KEY]
 
-    return await service_request(repo, request, request.app["handlers"])
+    return await service_request(repo, request, request.app[HANDLERS_KEY])
 
 
 def create_repo_app(
@@ -298,11 +304,11 @@ def create_repo_app(
     Returns: Configured aiohttp Application
     """
     app = web.Application()
-    app["repo"] = repo
+    app[REPO_KEY] = repo
     if handlers is None:
         handlers = dict(DEFAULT_HANDLERS)
-    app["handlers"] = handlers
-    app["dumb"] = dumb
+    app[HANDLERS_KEY] = handlers
+    app[DUMB_KEY] = dumb
     app.router.add_get("/info/refs", get_info_refs)
     app.router.add_post(
         "/{service:git-upload-pack|git-receive-pack}", handle_service_request

+ 52 - 43
dulwich/mbox.py

@@ -78,46 +78,52 @@ def split_mbox(
         raise ValueError(f"Output path is not a directory: {output_dir}")
 
     # Open the mbox file
+    mbox_obj: mailbox.mbox | None = None
     mbox_iter: Iterable[mailbox.mboxMessage]
     if isinstance(input_file, (str, bytes)):
         if isinstance(input_file, bytes):
             input_file = input_file.decode("utf-8")
-        mbox_iter = mailbox.mbox(input_file)
+        mbox_obj = mailbox.mbox(input_file)
+        mbox_iter = mbox_obj
     else:
         # For file-like objects, we need to read and parse manually
         mbox_iter = _parse_mbox_from_file(input_file)
 
-    output_files = []
-    msg_number = start_number
+    try:
+        output_files = []
+        msg_number = start_number
 
-    for message in mbox_iter:
-        # Format the output filename with the specified precision
-        output_filename = f"{msg_number:0{precision}d}"
-        output_file_path = output_path / output_filename
+        for message in mbox_iter:
+            # Format the output filename with the specified precision
+            output_filename = f"{msg_number:0{precision}d}"
+            output_file_path = output_path / output_filename
 
-        # Write the message to the output file
-        with open(output_file_path, "wb") as f:
-            message_bytes = bytes(message)
+            # Write the message to the output file
+            with open(output_file_path, "wb") as f:
+                message_bytes = bytes(message)
 
-            # Handle mboxrd format - reverse the escaping
-            if mboxrd:
-                message_bytes = _reverse_mboxrd_escaping(message_bytes)
+                # Handle mboxrd format - reverse the escaping
+                if mboxrd:
+                    message_bytes = _reverse_mboxrd_escaping(message_bytes)
 
-            # Handle CR/LF if needed
-            if not keep_cr:
-                message_bytes = message_bytes.replace(b"\r\n", b"\n")
+                # Handle CR/LF if needed
+                if not keep_cr:
+                    message_bytes = message_bytes.replace(b"\r\n", b"\n")
 
-            # Strip trailing newlines (mailbox module adds separator newlines)
-            message_bytes = message_bytes.rstrip(b"\n")
-            if message_bytes:
-                message_bytes += b"\n"
+                # Strip trailing newlines (mailbox module adds separator newlines)
+                message_bytes = message_bytes.rstrip(b"\n")
+                if message_bytes:
+                    message_bytes += b"\n"
 
-            f.write(message_bytes)
+                f.write(message_bytes)
 
-        output_files.append(str(output_file_path))
-        msg_number += 1
+            output_files.append(str(output_file_path))
+            msg_number += 1
 
-    return output_files
+        return output_files
+    finally:
+        if mbox_obj is not None:
+            mbox_obj.close()
 
 
 def split_maildir(
@@ -167,33 +173,36 @@ def split_maildir(
     # Open the Maildir
     md = mailbox.Maildir(str(maildir), factory=None)
 
-    # Get all messages and sort by their keys to ensure consistent ordering
-    sorted_keys = sorted(md.keys())
+    try:
+        # Get all messages and sort by their keys to ensure consistent ordering
+        sorted_keys = sorted(md.keys())
 
-    output_files = []
-    msg_number = start_number
+        output_files = []
+        msg_number = start_number
 
-    for key in sorted_keys:
-        message = md[key]
+        for key in sorted_keys:
+            message = md[key]
 
-        # Format the output filename with the specified precision
-        output_filename = f"{msg_number:0{precision}d}"
-        output_file_path = output_path / output_filename
+            # Format the output filename with the specified precision
+            output_filename = f"{msg_number:0{precision}d}"
+            output_file_path = output_path / output_filename
 
-        # Write the message to the output file
-        with open(output_file_path, "wb") as f:
-            message_bytes = bytes(message)
+            # Write the message to the output file
+            with open(output_file_path, "wb") as f:
+                message_bytes = bytes(message)
 
-            # Handle CR/LF if needed
-            if not keep_cr:
-                message_bytes = message_bytes.replace(b"\r\n", b"\n")
+                # Handle CR/LF if needed
+                if not keep_cr:
+                    message_bytes = message_bytes.replace(b"\r\n", b"\n")
 
-            f.write(message_bytes)
+                f.write(message_bytes)
 
-        output_files.append(str(output_file_path))
-        msg_number += 1
+            output_files.append(str(output_file_path))
+            msg_number += 1
 
-    return output_files
+        return output_files
+    finally:
+        md.close()
 
 
 def _parse_mbox_from_file(file_obj: BinaryIO) -> Iterator[mailbox.mboxMessage]:

+ 1 - 1
dulwich/objects.py

@@ -2098,7 +2098,7 @@ class Commit(ShaFile):
             gpgsig,
             message,
             extra,
-        ) = parse_commit(chunks)
+        ) = _parse_commit(chunks)
 
         self._tree = tree
         self._parents = [ObjectID(p) for p in parents]

+ 2 - 0
dulwich/repo.py

@@ -2746,6 +2746,8 @@ class MemoryRepo(BaseRepo):
         if self.filter_context is not None:
             self.filter_context.close()
             self.filter_context = None
+        # Close object store to release pack files
+        self.object_store.close()
 
     def do_commit(
         self,

+ 1 - 0
pyproject.toml

@@ -104,6 +104,7 @@ warn_return_any = false
 [tool.setuptools]
 packages = [
     "dulwich",
+    "dulwich.aiohttp",
     "dulwich.cloud",
     "dulwich.contrib",
     "dulwich.porcelain",

+ 2 - 0
tests/compat/test_aiohttp.py

@@ -94,6 +94,8 @@ class AiohttpServerTests(ServerTests):
             future.result(timeout=5)
             loop.call_soon_threadsafe(loop.stop)
             thread.join(timeout=1.0)
+            # Close the event loop to avoid ResourceWarning
+            loop.close()
 
         self.addCleanup(cleanup)
         self._server = runner

+ 6 - 5
tests/compat/test_server.py

@@ -77,12 +77,12 @@ class GitServerTestCase(ServerTests, CompatTestCase):
         server_thread.daemon = True  # Make thread daemon so it dies with main thread
         server_thread.start()
 
-        # Add cleanup in the correct order
+        # Add cleanup in the correct order - shutdown first, then close
         def cleanup_server():
             dul_server.shutdown()
-            dul_server.server_close()
-            # Give thread a moment to exit cleanly
+            # Give thread a moment to exit cleanly before closing socket
             server_thread.join(timeout=1.0)
+            dul_server.server_close()
 
         self.addCleanup(cleanup_server)
         self._server = dul_server
@@ -143,11 +143,12 @@ class GitServerSHA256TestCase(CompatTestCase):
         server_thread.daemon = True
         server_thread.start()
 
-        # Add cleanup
+        # Add cleanup - shutdown first, then close
         def cleanup_server():
             dul_server.shutdown()
-            dul_server.server_close()
+            # Give thread a moment to exit cleanly before closing socket
             server_thread.join(timeout=1.0)
+            dul_server.server_close()
 
         self.addCleanup(cleanup_server)
         self._server = dul_server