Add safe component editing and explicit legacy workspace transition
Why: Preserve a single Dev entry and independent repository ownership while allowing active feature branches to migrate without losing source or Git state. Evidence: SELF 29 real Git/Go workspace tests and ordinary component evidence/lock tests passed; detailed RED/GREEN runs are recorded by make evidence in the task card. Impact: Add fixed cloning, selected-version baselines, development overlays, npm source resolution, and independently preserved legacy snapshots. No other active worktree is modified. Refs: Versioned dependencies U2/U5; R15; feature/dependency-workspace
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
|
||||
from .common import WorkspaceError, run
|
||||
|
||||
|
||||
def display_origin(value):
|
||||
parsed = urlsplit(value)
|
||||
netloc = parsed.netloc
|
||||
if parsed.username and (parsed.scheme != "ssh" or parsed.username != "git" or parsed.password):
|
||||
netloc = "<redacted>@" + netloc.rsplit("@", 1)[-1]
|
||||
return urlunsplit((parsed.scheme, netloc, parsed.path,
|
||||
"<redacted>" if parsed.query else "", "<redacted>" if parsed.fragment else ""))
|
||||
|
||||
|
||||
class Git:
|
||||
def __init__(self, env):
|
||||
self.env = env
|
||||
|
||||
def call(self, path, *args, check=True):
|
||||
return run(["git", "-c", "gc.auto=0", "-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=false", *args], path, self.env, check)
|
||||
|
||||
def text(self, path, *args, check=True):
|
||||
return self.call(path, *args, check=check).stdout.strip()
|
||||
|
||||
def inspect(self, path):
|
||||
if not (path / ".git").exists():
|
||||
raise WorkspaceError(f"existing path is not an independent Git checkout: {path}")
|
||||
top = Path(self.text(path, "rev-parse", "--show-toplevel"))
|
||||
if top.resolve() != path.resolve():
|
||||
raise WorkspaceError("component path is not a Git root")
|
||||
gitdir = Path(self.text(path, "rev-parse", "--absolute-git-dir"))
|
||||
common = Path(self.text(path, "rev-parse", "--path-format=absolute", "--git-common-dir"))
|
||||
locks = sorted(str(file) for directory in {gitdir, common} for file in directory.rglob("*.lock"))
|
||||
operations = [name for name in ("MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "BISECT_LOG")
|
||||
if (gitdir / name).exists()]
|
||||
changes = self.text(path, "status", "--porcelain=v1", "--untracked-files=all")
|
||||
ignored = self.text(path, "ls-files", "--others", "--ignored", "--exclude-standard")
|
||||
head = self.text(path, "rev-parse", "--verify", "HEAD", check=False)
|
||||
branch = self.text(path, "symbolic-ref", "--quiet", "--short", "HEAD", check=False)
|
||||
worktrees = self.text(path, "worktree", "list", "--porcelain")
|
||||
return {"head": head or None, "branch": branch or None,
|
||||
"origin": display_origin(self.text(path, "remote", "get-url", "origin", check=False)),
|
||||
"dirty": bool(changes or ignored), "changes": changes.splitlines(), "ignored": ignored.splitlines(),
|
||||
"locks": locks, "operations": operations,
|
||||
"worktrees": [line[9:] for line in worktrees.splitlines() if line.startswith("worktree ")],
|
||||
"git_dir": str(gitdir), "common_dir": str(common)}
|
||||
|
||||
def safe(self, path, origin=None, clean=True, independent=True):
|
||||
actual = self.inspect(path)
|
||||
if origin is not None and actual["origin"] != origin:
|
||||
raise WorkspaceError(f"origin mismatch at {path}")
|
||||
if actual["locks"] or actual["operations"]:
|
||||
raise WorkspaceError(f"Git operation/lock in progress at {path}")
|
||||
if independent and (actual["git_dir"] != actual["common_dir"] or len(actual["worktrees"]) != 1):
|
||||
raise WorkspaceError(f"shared Git worktree ownership at {path}; owner must resolve it")
|
||||
if independent and ((path / ".git").is_symlink() or not (path / ".git").is_dir()
|
||||
or Path(actual["git_dir"]).resolve() != (path / ".git").resolve()):
|
||||
raise WorkspaceError(f"component Git metadata is owned outside its checkout: {path}")
|
||||
if clean and actual["dirty"]:
|
||||
raise WorkspaceError(f"dirty or ignored unique files at {path}; owner must save them")
|
||||
return actual
|
||||
|
||||
def gitlink(self, root, legacy):
|
||||
if not legacy:
|
||||
return None
|
||||
result = self.text(root, "ls-tree", "HEAD", "--", legacy, check=False)
|
||||
if result.startswith("160000 commit "):
|
||||
return result.split()[2]
|
||||
return None
|
||||
Reference in New Issue
Block a user