feat(git): submodule and remote handling

This commit is contained in:
Tiara Rodney 2026-03-04 18:10:18 +01:00
parent 5bf4a7eee4
commit c4fb29f694
No known key found for this signature in database
GPG key ID: 5CD8EC1D46106723

View file

@ -146,6 +146,18 @@ def show_ref(repo: Path) -> str:
return "" return ""
def ls_remote(repo: Path, remote: str) -> str:
"""Return the raw output of ``git ls-remote <remote>``.
Returns an empty string if the remote has no refs or on error.
"""
try:
result = _run(["ls-remote", remote], cwd=repo)
return result.stdout
except GitError:
return ""
def mirror_push(repo: Path, remote: str) -> None: def mirror_push(repo: Path, remote: str) -> None:
"""Push the full mirror to a remote. """Push the full mirror to a remote.
@ -260,22 +272,39 @@ def has_submodule(repo: Path, path: str) -> bool:
"""Check whether a submodule is registered at *path*. """Check whether a submodule is registered at *path*.
Reads ``.gitmodules`` to determine whether the submodule exists. Reads ``.gitmodules`` to determine whether the submodule exists.
*path* is resolved relative to *repo*, then compared against
the repository root so the check works when *repo* is a
subdirectory of the actual git working tree.
Returns False if ``.gitmodules`` does not exist. Returns False if ``.gitmodules`` does not exist.
""" """
gitmodules = repo / ".gitmodules" try:
toplevel = Path(
_run(
["rev-parse", "--show-toplevel"], cwd=repo,
).stdout.strip()
)
except GitError:
return False
gitmodules = toplevel / ".gitmodules"
if not gitmodules.is_file(): if not gitmodules.is_file():
return False return False
# Resolve the full path relative to the repo root
full_path = (repo / path).resolve()
try:
rel_path = str(full_path.relative_to(toplevel.resolve()))
except ValueError:
return False
try: try:
result = _run( result = _run(
["config", "--file", ".gitmodules", ["config", "--file", str(gitmodules),
"--get-regexp", r"submodule\..*\.path"], "--get-regexp", r"submodule\..*\.path"],
cwd=repo, cwd=toplevel,
) )
except GitError: except GitError:
return False return False
for line in result.stdout.splitlines(): for line in result.stdout.splitlines():
parts = line.split(None, 1) parts = line.split(None, 1)
if len(parts) == 2 and parts[1] == path: if len(parts) == 2 and parts[1] == rel_path:
return True return True
return False return False
@ -306,3 +335,11 @@ def submodule_update(repo: Path, path: str) -> None:
default_branch = result.stdout.strip() default_branch = result.stdout.strip()
_run(["checkout", default_branch], cwd=sub_path) _run(["checkout", default_branch], cwd=sub_path)
logger.info("Updated submodule %s to %s", path, default_branch) logger.info("Updated submodule %s to %s", path, default_branch)
def submodule_checkout(repo: Path, path: str, ref: str) -> None:
"""Fetch and checkout a specific ref in a submodule."""
sub_path = repo / path
_run(["fetch", "origin"], cwd=sub_path)
_run(["checkout", ref], cwd=sub_path)
logger.info("Checked out submodule %s at %s", path, ref)