from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
from typing import List, Optional
def _cmd_text(cmd: List[str]) -> str:
return " ".join(cmd)
def _hint_for_cmd(cmd: List[str], returncode: int) -> str | None:
if not cmd or cmd[0] != "git":
return None
if cmd[:3] == ["git", "config", "--get"]:
key = cmd[-1] if cmd else "<key>"
return f"Set it with `git config {key} <value>` from the super-repo root."
if cmd[:4] == ["git", "submodule", "update", "--init"]:
return "Verify the submodule path exists in `.gitmodules`, then run `git submodule sync --recursive`."
if cmd[:2] == ["git", "fetch"]:
return "Check remote/branch names and auth. Try `git remote -v` and fetch manually."
if cmd[:2] == ["git", "checkout"]:
return "Ensure the branch exists upstream and your worktree has no conflicting local changes."
if cmd[:3] == ["git", "merge", "--ff-only"]:
return "Local branch diverged. Rebase/reset the submodule branch or resolve divergence manually."
if cmd[:2] == ["git", "commit"]:
return "Ensure git user.name/user.email are configured and there are staged changes."
return f"Run `{_cmd_text(cmd)}` manually for full git output."
def run(cmd: List[str], *, capture: bool = False, cwd: Optional[Path] = None) -> str | None:
cmd_display = _cmd_text(cmd)
run_cwd = str(cwd) if cwd is not None else None
where = f" (cwd={cwd})" if cwd is not None else ""
print("Running:", cmd_display)
try:
if capture:
result = subprocess.run(
cmd,
check=True,
text=True,
capture_output=True,
cwd=run_cwd,
)
return result.stdout.strip()
subprocess.run(cmd, check=True, cwd=run_cwd)
return None
except FileNotFoundError as e:
raise SystemExit(
f"Required command '{e.filename}' was not found while running: {cmd_display}{where}. "
"Install git and ensure it is on your PATH."
) from e
except subprocess.CalledProcessError as e:
hint = _hint_for_cmd(cmd, e.returncode)
raise SystemExit(
f"Command failed with exit code {e.returncode}: {cmd_display}{where}"
+ (f". Hint: {hint}" if hint else "")
) from e
def get_config(key: str) -> str:
value = run(["git", "config", "--get", key], capture=True)
if not value:
raise SystemExit(
f"Empty git config key: {key}. "
f"Set it with `git config {key} <value>`."
)
return value
def is_git_repo(path: Path) -> bool:
if (path / ".git").is_dir():
return True
if (path / ".git").is_file(): return True
try:
subprocess.run(
["git", "rev-parse", "--is-inside-work-tree"],
cwd=path,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
)
return True
except subprocess.CalledProcessError:
return False
except FileNotFoundError as e:
raise SystemExit(
"Required command 'git' was not found while checking repository state."
) from e
def main() -> None:
script_dir = Path(__file__).parent.resolve()
repo_root = script_dir.parent
os.chdir(repo_root)
prefix = "sedsnet"
submodule_path = repo_root / prefix
if not submodule_path.exists():
raise SystemExit(
f"Path '{prefix}' does not exist. "
"Run this script from the expected super-repo layout containing that submodule path."
)
if not is_git_repo(submodule_path):
raise SystemExit(
f"'{prefix}' is NOT a git repository.\n"
"This looks like a subtree or a plain directory.\n"
"Refusing to run submodule update logic.\n"
"Hint: Use `subtree_update_no_stash.py` if this checkout uses a subtree."
)
try:
get_config(f"submodule.{prefix}.url")
except SystemExit:
raise SystemExit(
f"'{prefix}' is a git repo, but NOT registered as a submodule.\n"
"If this is a subtree, use the subtree updater instead.\n"
f"Otherwise add/fix `submodule.{prefix}.url` and `submodule.{prefix}.branch` in git config."
)
url = get_config(f"submodule.{prefix}.url")
branch = get_config(f"submodule.{prefix}.branch")
print(f"Updating submodule: {prefix}")
print(f" url: {url}")
print(f" branch: {branch}")
run(["git", "submodule", "update", "--init", "--", prefix])
run(["git", "fetch", "origin", branch], cwd=submodule_path)
existing = run(["git", "branch", "--list", branch], capture=True, cwd=submodule_path)
if existing:
run(["git", "checkout", branch], cwd=submodule_path)
else:
run(["git", "checkout", "-b", branch, f"origin/{branch}"], cwd=submodule_path)
run(["git", "merge", "--ff-only", f"origin/{branch}"], cwd=submodule_path)
run(["git", "add", prefix])
try:
diff_proc = subprocess.run(
["git", "diff", "--cached", "--quiet", "--", prefix]
)
except FileNotFoundError as e:
raise SystemExit(
"Required command 'git' was not found while checking staged changes. "
"Install git and ensure it is on your PATH."
) from e
if diff_proc.returncode == 0:
print("Submodule is already at latest commit; nothing to commit.")
return
commit_msg = f"chore(submodule): Update submodule {prefix} to latest {branch}"
run(["git", "commit", "-m", commit_msg])
print("Done:", commit_msg)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\nexiting...")
exit(0)
except Exception as e:
print(f"Error: Unexpected failure: {e}", file=sys.stderr)
raise SystemExit(1) from e