feat(@tools): ✨ add pull blocker self-healing logic
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
68c848dc56
commit
006bde3f6c
1 changed files with 46 additions and 1 deletions
|
|
@ -322,6 +322,48 @@ def enforce_hostname(name: str) -> None:
|
|||
# Pull — propagate declared truth + this agent's own code
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _heal_pull_blockers(as_owner, repo_root: str, err: str) -> bool:
|
||||
"""git refuses an ff merge over working files it would touch — even when
|
||||
their content is byte-identical to the incoming commit (e.g. a tree that
|
||||
was bridged by rsync before the same files landed upstream). Clearing only
|
||||
exact matches is lossless: untracked twins are removed (the merge recreates
|
||||
them verbatim), tracked edits matching the target are reset to HEAD (the
|
||||
merge fast-forwards them straight back). Any file that differs stays put
|
||||
and keeps blocking — real local work is never destroyed."""
|
||||
paths: list[str] = []
|
||||
collect = False
|
||||
for line in err.splitlines():
|
||||
if "would be overwritten by" in line:
|
||||
collect = True
|
||||
elif collect and line.startswith(("\t", " ")):
|
||||
paths.append(line.strip())
|
||||
else:
|
||||
collect = False
|
||||
if not paths:
|
||||
return False
|
||||
healed = 0
|
||||
for p in paths:
|
||||
rc, want, _ = as_owner(["rev-parse", f"@{{u}}:{p}"], 15)
|
||||
if rc != 0:
|
||||
return False
|
||||
rc, have, _ = as_owner(["hash-object", "--", p], 15)
|
||||
if rc != 0 or have.strip() != want.strip():
|
||||
logger.warning("pull blocked by %s which differs from upstream — leaving it", p)
|
||||
return False
|
||||
if as_owner(["ls-files", "--error-unmatch", "--", p], 15)[0] == 0:
|
||||
if as_owner(["checkout", "--", p], 15)[0] != 0:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
os.unlink(os.path.join(repo_root, p))
|
||||
except OSError as exc:
|
||||
logger.warning("pull self-heal: cannot remove %s: %s", p, exc)
|
||||
return False
|
||||
healed += 1
|
||||
logger.info("pull self-heal: cleared %d file(s) byte-identical to upstream", healed)
|
||||
return True
|
||||
|
||||
|
||||
def git_pull(repo_root: str, ctx: dict) -> bool:
|
||||
"""ff-only pull as the REPO OWNER (root-owned .git objects would break the
|
||||
autocommit service). Returns True iff HEAD moved (caller exits to restart)."""
|
||||
|
|
@ -343,7 +385,10 @@ def git_pull(repo_root: str, ctx: dict) -> bool:
|
|||
rc, before, _ = as_owner(["rev-parse", "HEAD"], 15)
|
||||
if rc != 0:
|
||||
return False
|
||||
rc, _, err = as_owner(["pull", "--ff-only", "--quiet"])
|
||||
for _ in range(3): # untracked + tracked blockers can surface in separate aborts
|
||||
rc, _, err = as_owner(["pull", "--ff-only", "--quiet"])
|
||||
if rc == 0 or not _heal_pull_blockers(as_owner, repo_root, err):
|
||||
break
|
||||
if rc != 0:
|
||||
logger.warning("git pull failed (keeping current code/config): %s", err.strip()[:200])
|
||||
return False
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue