Bladeren bron

Splitout submodule

Jelmer Vernooij 1 maand geleden
bovenliggende
commit
112cdb45bc
2 gewijzigde bestanden met toevoegingen van 267 en 214 verwijderingen
  1. 7 214
      dulwich/porcelain/__init__.py
  2. 260 0
      dulwich/porcelain/submodule.py

+ 7 - 214
dulwich/porcelain/__init__.py

@@ -315,7 +315,7 @@ from ..client import (
     SendPackResult,
     get_transport_and_path,
 )
-from ..config import Config, ConfigFile, StackedConfig, read_submodules
+from ..config import Config, StackedConfig
 from ..diff_tree import (
     CHANGE_ADD,
     CHANGE_COPY,
@@ -336,7 +336,6 @@ from ..index import (
     _fs_to_tree_path,
     blob_from_path_and_stat,
     build_file_from_blob,
-    build_index_from_tree,
     get_unstaged_changes,
     symlink,
     update_working_tree,
@@ -418,6 +417,12 @@ from .lfs import (
     lfs_track,
     lfs_untrack,
 )
+from .submodule import (
+    submodule_add,
+    submodule_init,
+    submodule_list,
+    submodule_update,
+)
 
 # Module level tuple definition for status output
 GitStatus = namedtuple("GitStatus", "staged unstaged untracked")
@@ -2336,218 +2341,6 @@ def _canonical_part(url: str) -> str:
     return name
 
 
-def submodule_add(
-    repo: str | os.PathLike[str] | Repo,
-    url: str,
-    path: str | os.PathLike[str] | None = None,
-    name: str | None = None,
-) -> None:
-    """Add a new submodule.
-
-    Args:
-      repo: Path to repository
-      url: URL of repository to add as submodule
-      path: Path where submodule should live
-      name: Name for the submodule
-    """
-    with open_repo_closing(repo) as r:
-        if path is None:
-            path = os.path.relpath(_canonical_part(url), r.path)
-        if name is None:
-            name = os.fsdecode(path) if path is not None else None
-
-        if name is None:
-            raise Error("Submodule name must be specified or derivable from path")
-
-        # TODO(jelmer): Move this logic to dulwich.submodule
-        gitmodules_path = os.path.join(r.path, ".gitmodules")
-        try:
-            config = ConfigFile.from_path(gitmodules_path)
-        except FileNotFoundError:
-            config = ConfigFile()
-            config.path = gitmodules_path
-        config.set(("submodule", name), "url", url)
-        config.set(("submodule", name), "path", os.fsdecode(path))
-        config.write_to_path()
-
-
-def submodule_init(repo: str | os.PathLike[str] | Repo) -> None:
-    """Initialize submodules.
-
-    Args:
-      repo: Path to repository
-    """
-    with open_repo_closing(repo) as r:
-        config = r.get_config()
-        gitmodules_path = os.path.join(r.path, ".gitmodules")
-        for path, url, name in read_submodules(gitmodules_path):
-            config.set((b"submodule", name), b"active", True)
-            config.set((b"submodule", name), b"url", url)
-        config.write_to_path()
-
-
-def submodule_list(repo: RepoPath) -> Iterator[tuple[str, str]]:
-    """List submodules.
-
-    Args:
-      repo: Path to repository
-    """
-    from ..submodule import iter_cached_submodules
-
-    with open_repo_closing(repo) as r:
-        head_commit = r[r.head()]
-        assert isinstance(head_commit, Commit)
-        for path, sha in iter_cached_submodules(r.object_store, head_commit.tree):
-            yield path.decode(DEFAULT_ENCODING), sha.decode(DEFAULT_ENCODING)
-
-
-def submodule_update(
-    repo: str | os.PathLike[str] | Repo,
-    paths: Sequence[str | bytes | os.PathLike[str]] | None = None,
-    init: bool = False,
-    force: bool = False,
-    recursive: bool = False,
-    errstream: BinaryIO | None = None,
-) -> None:
-    """Update submodules.
-
-    Args:
-      repo: Path to repository
-      paths: Optional list of specific submodule paths to update. If None, updates all.
-      init: If True, initialize submodules first
-      force: Force update even if local changes exist
-      recursive: If True, recursively update nested submodules
-      errstream: Error stream for error messages
-    """
-    from ..submodule import iter_cached_submodules
-
-    with open_repo_closing(repo) as r:
-        if init:
-            submodule_init(r)
-
-        config = r.get_config()
-        gitmodules_path = os.path.join(r.path, ".gitmodules")
-
-        # Get list of submodules to update
-        submodules_to_update = []
-        head_commit = r[r.head()]
-        assert isinstance(head_commit, Commit)
-        for path, sha in iter_cached_submodules(r.object_store, head_commit.tree):
-            path_str = (
-                path.decode(DEFAULT_ENCODING) if isinstance(path, bytes) else path
-            )
-            if paths is None or path_str in paths:
-                submodules_to_update.append((path, sha))
-
-        # Read submodule configuration
-        for path, target_sha in submodules_to_update:
-            path_str = (
-                path.decode(DEFAULT_ENCODING) if isinstance(path, bytes) else path
-            )
-
-            # Find the submodule name from ..gitmodules
-            submodule_name: bytes | None = None
-            for sm_path, sm_url, sm_name in read_submodules(gitmodules_path):
-                if sm_path == path:
-                    submodule_name = sm_name
-                    break
-
-            if not submodule_name:
-                continue
-
-            # Get the URL from config
-            section = (
-                b"submodule",
-                submodule_name
-                if isinstance(submodule_name, bytes)
-                else submodule_name.encode(),
-            )
-            try:
-                url_value = config.get(section, b"url")
-                if isinstance(url_value, bytes):
-                    url = url_value.decode(DEFAULT_ENCODING)
-                else:
-                    url = url_value
-            except KeyError:
-                # URL not in config, skip this submodule
-                continue
-
-            # Get or create the submodule repository paths
-            submodule_path = os.path.join(r.path, path_str)
-            submodule_git_dir = os.path.join(r.controldir(), "modules", path_str)
-
-            # Clone or fetch the submodule
-            if not os.path.exists(submodule_git_dir):
-                # Clone the submodule as bare repository
-                os.makedirs(os.path.dirname(submodule_git_dir), exist_ok=True)
-
-                # Clone to the git directory
-                sub_repo = clone(url, submodule_git_dir, bare=True, checkout=False)
-                sub_repo.close()
-
-                # Create the submodule directory if it doesn't exist
-                if not os.path.exists(submodule_path):
-                    os.makedirs(submodule_path)
-
-                # Create .git file in the submodule directory
-                relative_git_dir = os.path.relpath(submodule_git_dir, submodule_path)
-                git_file_path = os.path.join(submodule_path, ".git")
-                with open(git_file_path, "w") as f:
-                    f.write(f"gitdir: {relative_git_dir}\n")
-
-                # Set up working directory configuration
-                with open_repo_closing(submodule_git_dir) as sub_repo:
-                    sub_config = sub_repo.get_config()
-                    sub_config.set(
-                        (b"core",),
-                        b"worktree",
-                        os.path.abspath(submodule_path).encode(),
-                    )
-                    sub_config.write_to_path()
-
-                    # Checkout the target commit
-                    sub_repo.refs[HEADREF] = target_sha
-
-                    # Build the index and checkout files
-                    tree = sub_repo[target_sha]
-                    if hasattr(tree, "tree"):  # If it's a commit, get the tree
-                        tree_id = tree.tree
-                    else:
-                        tree_id = target_sha
-
-                    build_index_from_tree(
-                        submodule_path,
-                        sub_repo.index_path(),
-                        sub_repo.object_store,
-                        tree_id,
-                    )
-            else:
-                # Fetch and checkout in existing submodule
-                with open_repo_closing(submodule_git_dir) as sub_repo:
-                    # Fetch from remote
-                    client, path_segments = get_transport_and_path(url)
-                    client.fetch(path_segments.encode(), sub_repo)
-
-                    # Update to the target commit
-                    sub_repo.refs[HEADREF] = target_sha
-
-                    # Reset the working directory
-                    reset(sub_repo, "hard", target_sha)
-
-            # Recursively update nested submodules if requested
-            if recursive:
-                submodule_gitmodules = os.path.join(submodule_path, ".gitmodules")
-                if os.path.exists(submodule_gitmodules):
-                    submodule_update(
-                        submodule_path,
-                        paths=None,
-                        init=True,  # Always initialize nested submodules
-                        force=force,
-                        recursive=True,
-                        errstream=errstream,
-                    )
-
-
 def tag_create(
     repo: RepoPath,
     tag: str | bytes,

+ 260 - 0
dulwich/porcelain/submodule.py

@@ -0,0 +1,260 @@
+# submodule.py -- Submodule porcelain
+# Copyright (C) 2013 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
+"""Porcelain functions for working with submodules."""
+
+import os
+from collections.abc import Iterator, Sequence
+from typing import BinaryIO, TYPE_CHECKING
+
+from ..config import ConfigFile, read_submodules
+from ..objects import Commit
+from ..repo import Repo
+
+
+if TYPE_CHECKING:
+    from . import RepoPath
+
+
+def submodule_add(
+    repo: str | os.PathLike[str] | Repo,
+    url: str,
+    path: str | os.PathLike[str] | None = None,
+    name: str | None = None,
+) -> None:
+    """Add a new submodule.
+
+    Args:
+      repo: Path to repository
+      url: URL of repository to add as submodule
+      path: Path where submodule should live
+      name: Name for the submodule
+    """
+    from . import Error, _canonical_part, open_repo_closing
+
+    with open_repo_closing(repo) as r:
+        if path is None:
+            path = os.path.relpath(_canonical_part(url), r.path)
+        if name is None:
+            name = os.fsdecode(path) if path is not None else None
+
+        if name is None:
+            raise Error("Submodule name must be specified or derivable from path")
+
+        # TODO(jelmer): Move this logic to dulwich.submodule
+        gitmodules_path = os.path.join(r.path, ".gitmodules")
+        try:
+            config = ConfigFile.from_path(gitmodules_path)
+        except FileNotFoundError:
+            config = ConfigFile()
+            config.path = gitmodules_path
+        config.set(("submodule", name), "url", url)
+        config.set(("submodule", name), "path", os.fsdecode(path))
+        config.write_to_path()
+
+
+def submodule_init(repo: str | os.PathLike[str] | Repo) -> None:
+    """Initialize submodules.
+
+    Args:
+      repo: Path to repository
+    """
+    from . import open_repo_closing
+
+    with open_repo_closing(repo) as r:
+        config = r.get_config()
+        gitmodules_path = os.path.join(r.path, ".gitmodules")
+        for path, url, name in read_submodules(gitmodules_path):
+            config.set((b"submodule", name), b"active", True)
+            config.set((b"submodule", name), b"url", url)
+        config.write_to_path()
+
+
+def submodule_list(repo: "RepoPath") -> Iterator[tuple[str, str]]:
+    """List submodules.
+
+    Args:
+      repo: Path to repository
+    """
+    from ..submodule import iter_cached_submodules
+    from . import DEFAULT_ENCODING, open_repo_closing
+
+    with open_repo_closing(repo) as r:
+        head_commit = r[r.head()]
+        assert isinstance(head_commit, Commit)
+        for path, sha in iter_cached_submodules(r.object_store, head_commit.tree):
+            yield path.decode(DEFAULT_ENCODING), sha.decode(DEFAULT_ENCODING)
+
+
+def submodule_update(
+    repo: str | os.PathLike[str] | Repo,
+    paths: Sequence[str | bytes | os.PathLike[str]] | None = None,
+    init: bool = False,
+    force: bool = False,
+    recursive: bool = False,
+    errstream: BinaryIO | None = None,
+) -> None:
+    """Update submodules.
+
+    Args:
+      repo: Path to repository
+      paths: Optional list of specific submodule paths to update. If None, updates all.
+      init: If True, initialize submodules first
+      force: Force update even if local changes exist
+      recursive: If True, recursively update nested submodules
+      errstream: Error stream for error messages
+    """
+    from ..client import get_transport_and_path
+    from ..index import build_index_from_tree
+    from ..refs import HEADREF
+    from ..submodule import iter_cached_submodules
+    from . import (
+        DEFAULT_ENCODING,
+        clone,
+        open_repo_closing,
+        reset,
+    )
+
+    with open_repo_closing(repo) as r:
+        if init:
+            submodule_init(r)
+
+        config = r.get_config()
+        gitmodules_path = os.path.join(r.path, ".gitmodules")
+
+        # Get list of submodules to update
+        submodules_to_update = []
+        head_commit = r[r.head()]
+        assert isinstance(head_commit, Commit)
+        for path, sha in iter_cached_submodules(r.object_store, head_commit.tree):
+            path_str = (
+                path.decode(DEFAULT_ENCODING) if isinstance(path, bytes) else path
+            )
+            if paths is None or path_str in paths:
+                submodules_to_update.append((path, sha))
+
+        # Read submodule configuration
+        for path, target_sha in submodules_to_update:
+            path_str = (
+                path.decode(DEFAULT_ENCODING) if isinstance(path, bytes) else path
+            )
+
+            # Find the submodule name from ..gitmodules
+            submodule_name: bytes | None = None
+            for sm_path, sm_url, sm_name in read_submodules(gitmodules_path):
+                if sm_path == path:
+                    submodule_name = sm_name
+                    break
+
+            if not submodule_name:
+                continue
+
+            # Get the URL from config
+            section = (
+                b"submodule",
+                submodule_name
+                if isinstance(submodule_name, bytes)
+                else submodule_name.encode(),
+            )
+            try:
+                url_value = config.get(section, b"url")
+                if isinstance(url_value, bytes):
+                    url = url_value.decode(DEFAULT_ENCODING)
+                else:
+                    url = url_value
+            except KeyError:
+                # URL not in config, skip this submodule
+                continue
+
+            # Get or create the submodule repository paths
+            submodule_path = os.path.join(r.path, path_str)
+            submodule_git_dir = os.path.join(r.controldir(), "modules", path_str)
+
+            # Clone or fetch the submodule
+            if not os.path.exists(submodule_git_dir):
+                # Clone the submodule as bare repository
+                os.makedirs(os.path.dirname(submodule_git_dir), exist_ok=True)
+
+                # Clone to the git directory
+                sub_repo = clone(url, submodule_git_dir, bare=True, checkout=False)
+                sub_repo.close()
+
+                # Create the submodule directory if it doesn't exist
+                if not os.path.exists(submodule_path):
+                    os.makedirs(submodule_path)
+
+                # Create .git file in the submodule directory
+                relative_git_dir = os.path.relpath(submodule_git_dir, submodule_path)
+                git_file_path = os.path.join(submodule_path, ".git")
+                with open(git_file_path, "w") as f:
+                    f.write(f"gitdir: {relative_git_dir}\n")
+
+                # Set up working directory configuration
+                with open_repo_closing(submodule_git_dir) as sub_repo:
+                    sub_config = sub_repo.get_config()
+                    sub_config.set(
+                        (b"core",),
+                        b"worktree",
+                        os.path.abspath(submodule_path).encode(),
+                    )
+                    sub_config.write_to_path()
+
+                    # Checkout the target commit
+                    sub_repo.refs[HEADREF] = target_sha
+
+                    # Build the index and checkout files
+                    tree = sub_repo[target_sha]
+                    if hasattr(tree, "tree"):  # If it's a commit, get the tree
+                        tree_id = tree.tree
+                    else:
+                        tree_id = target_sha
+
+                    build_index_from_tree(
+                        submodule_path,
+                        sub_repo.index_path(),
+                        sub_repo.object_store,
+                        tree_id,
+                    )
+            else:
+                # Fetch and checkout in existing submodule
+                with open_repo_closing(submodule_git_dir) as sub_repo:
+                    # Fetch from remote
+                    client, path_segments = get_transport_and_path(url)
+                    client.fetch(path_segments.encode(), sub_repo)
+
+                    # Update to the target commit
+                    sub_repo.refs[HEADREF] = target_sha
+
+                    # Reset the working directory
+                    reset(sub_repo, "hard", target_sha)
+
+            # Recursively update nested submodules if requested
+            if recursive:
+                submodule_gitmodules = os.path.join(submodule_path, ".gitmodules")
+                if os.path.exists(submodule_gitmodules):
+                    submodule_update(
+                        submodule_path,
+                        paths=None,
+                        init=True,  # Always initialize nested submodules
+                        force=force,
+                        recursive=True,
+                        errstream=errstream,
+                    )