Ver Fonte

Add ensure_submodule_placeholder function to dulwich.submodule

This adds a new function to create minimal submodule placeholder
directories with .git files pointing to the correct git directory.
This functionality is used by update_working_tree when handling
submodule entries.
Jelmer Vernooij há 1 mês atrás
pai
commit
cde3e8843f
2 ficheiros alterados com 76 adições e 2 exclusões
  1. 45 1
      dulwich/submodule.py
  2. 31 1
      tests/test_submodule.py

+ 45 - 1
dulwich/submodule.py

@@ -21,14 +21,16 @@
 
 """Working with Git submodules."""
 
+import os
 from collections.abc import Iterator
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Union
 
 from .object_store import iter_tree_contents
 from .objects import S_ISGITLINK
 
 if TYPE_CHECKING:
     from .object_store import ObjectContainer
+    from .repo import BaseRepo
 
 
 def iter_cached_submodules(
@@ -46,3 +48,45 @@ def iter_cached_submodules(
     for entry in iter_tree_contents(store, root_tree_id):
         if S_ISGITLINK(entry.mode):
             yield entry.path, entry.sha
+
+
+def ensure_submodule_placeholder(
+    repo: "BaseRepo", 
+    submodule_path: Union[str, bytes],
+) -> None:
+    """Create a submodule placeholder directory with .git file.
+    
+    This creates the minimal structure needed for a submodule:
+    - The submodule directory
+    - A .git file pointing to the submodule's git directory
+    
+    Args:
+      repo: Parent repository
+      submodule_path: Path to the submodule relative to repo root
+    """
+    # Ensure path is bytes
+    if isinstance(submodule_path, str):
+        submodule_path = submodule_path.encode()
+    
+    # Get repo path as bytes
+    repo_path = repo.path if isinstance(repo.path, bytes) else repo.path.encode()
+    
+    # Create full path to submodule
+    full_path = os.path.join(repo_path, submodule_path)
+    
+    # Create submodule directory if it doesn't exist
+    if not os.path.exists(full_path):
+        os.makedirs(full_path)
+    
+    # Create .git file pointing to the submodule's git directory
+    git_filename = b".git" if isinstance(full_path, bytes) else ".git"
+    git_file_path = os.path.join(full_path, git_filename)
+    if not os.path.exists(git_file_path):
+        # Submodule git directories are typically stored in .git/modules/<name>
+        # The relative path from the submodule to the parent's .git directory
+        # depends on the submodule's depth
+        depth = submodule_path.count(b'/') + 1
+        relative_git_dir = b"../" * depth + b".git/modules/" + submodule_path
+        
+        with open(git_file_path, "wb") as f:
+            f.write(b"gitdir: " + relative_git_dir + b"\n")

+ 31 - 1
tests/test_submodule.py

@@ -30,7 +30,7 @@ from dulwich.objects import (
     Tree,
 )
 from dulwich.repo import Repo
-from dulwich.submodule import iter_cached_submodules
+from dulwich.submodule import ensure_submodule_placeholder, iter_cached_submodules
 
 from . import TestCase
 
@@ -115,3 +115,33 @@ class SubmoduleTests(TestCase):
         path, sha = submodules[0]
         self.assertEqual(b"submodule", path)
         self.assertEqual(submodule_sha, sha)
+
+    def test_ensure_submodule_placeholder(self) -> None:
+        """Test creating submodule placeholder directories."""
+        # Create a repository
+        repo_path = os.path.join(self.test_dir, "testrepo")
+        repo = Repo.init(repo_path, mkdir=True)
+        
+        # Test creating a simple submodule placeholder
+        ensure_submodule_placeholder(repo, b"libs/mylib")
+        
+        # Check that the directory was created
+        submodule_path = os.path.join(repo_path, "libs", "mylib")
+        self.assertTrue(os.path.isdir(submodule_path))
+        
+        # Check that the .git file was created
+        git_file_path = os.path.join(submodule_path, ".git")
+        self.assertTrue(os.path.isfile(git_file_path))
+        
+        # Check the content of the .git file
+        with open(git_file_path, "rb") as f:
+            content = f.read()
+        self.assertEqual(b"gitdir: ../../.git/modules/libs/mylib\n", content)
+        
+        # Test with string path
+        ensure_submodule_placeholder(repo, "libs/another")
+        another_path = os.path.join(repo_path, "libs", "another")
+        self.assertTrue(os.path.isdir(another_path))
+        
+        # Test idempotency - calling again should not fail
+        ensure_submodule_placeholder(repo, b"libs/mylib")