SXXXXXXX_ProjectUtility/projectutility/core/submodule_manager.py
2025-12-01 08:29:48 +01:00

75 lines
2.7 KiB
Python

"""projectutility.core.submodule_manager
Utilities to ensure Git submodules are initialized/updated for a tool repo.
This module uses the system `git` command via subprocess to avoid
adding a hard dependency on GitPython for submodule operations.
"""
import logging
import os
import subprocess
from typing import List, Optional, Dict
logger = logging.getLogger(__name__)
def ensure_submodules(
repo_root: str, submodules: Optional[List[Dict[str, str]]] = None, timeout: int = 300
) -> bool:
"""Ensure submodules are initialized and updated for a repository.
Args:
repo_root: absolute path to the repository root (where .git lives).
submodules: optional list of dicts describing submodules. Each dict may
contain a `path` key (relative path inside repo). If None,
this will attempt to update the whole `external` directory
if it exists, otherwise run a global submodule update.
timeout: max seconds to wait for the git command.
Returns:
True if git submodule update completed successfully, False otherwise.
"""
if not os.path.isdir(repo_root):
logger.error("Submodule update: repo_root not found: %s", repo_root)
return False
cmd = ["git", "-C", repo_root, "submodule", "update", "--init", "--recursive"]
# Build path list for git command
if submodules:
paths = []
for entry in submodules:
p = entry.get("path") if isinstance(entry, dict) else None
if p:
paths.append(p)
if paths:
cmd += paths
else:
# Fallback: if repo has 'external' directory, limit to it
external = os.path.join(repo_root, "external")
if os.path.isdir(external):
cmd += ["external"]
logger.info("Running submodule update: %s", " ".join(cmd))
try:
proc = subprocess.run(
cmd, check=True, capture_output=True, text=True, timeout=timeout
)
if proc.stdout:
logger.debug("git submodule stdout: %s", proc.stdout)
if proc.stderr:
logger.debug("git submodule stderr: %s", proc.stderr)
logger.info("Submodule update completed for %s", repo_root)
return True
except subprocess.CalledProcessError as e:
logger.error(
"git submodule update failed for %s: %s", repo_root, e.stderr or str(e)
)
return False
except subprocess.TimeoutExpired:
logger.error("git submodule update timeout for %s", repo_root)
return False
except Exception as e:
logger.exception("Unexpected error while updating submodules for %s: %s", repo_root, e)
return False