gitgud-ai / app /services /danger_zone_service.py
CodeCommunity's picture
Update app/services/danger_zone_service.py
efa0c46 verified
Raw History Blame Contribute Delete
3.3 kB
import tempfile
import shutil
import logging
from typing import List, Dict, Any
logger = logging.getLogger(__name__)
class DangerZoneService:
@staticmethod
def simulate_destructive_action(
owner: str,
repo_name: str,
action: str,
target_ref: str,
current_local_shas: List[str] = None,
) -> Dict[str, Any]:
"""
Clones the repo into an ephemeral temp directory, performs the action,
and returns what would be lost. The temp dir is always cleaned up.
"""
# Lazy import so the whole app can still start even if git binary is missing
try:
from git import Repo
except ImportError as e:
logger.error(f"GitPython / git binary not available: {e}")
return {
"wouldLose": [],
"wouldRewrite": [],
"conflicts": [],
"summary": "Simulation unavailable: git binary is missing on the server.",
"error": "GIT_BINARY_MISSING"
}
temp_dir = tempfile.mkdtemp(prefix="gitgud_danger_")
repo_url = f"https://github.com/{owner}/{repo_name}.git"
try:
logger.info(f"Cloning {repo_url} into isolation dir for simulation...")
repo = Repo.clone_from(repo_url, temp_dir, depth=100)
before_shas = {c.hexsha for c in repo.iter_commits()}
if action in ("hard_reset", "force_push"):
repo.git.reset("--hard", target_ref)
else:
repo.git.reset("--hard", target_ref)
after_shas = {c.hexsha for c in repo.iter_commits()}
lost_shas = before_shas - after_shas
would_lose = []
for sha in lost_shas:
try:
commit = repo.commit(sha)
would_lose.append({
"id": sha[:7],
"author": commit.author.name if commit.author else "unknown",
"message": commit.summary,
"timestamp": commit.authored_date,
})
except Exception:
continue
if current_local_shas:
local_set = set(current_local_shas)
would_lose = [
c for c in would_lose
if any(c["id"] in s or s.startswith(c["id"]) for s in local_set)
]
summary = (
f"This {action.replace('_', ' ')} will eliminate "
f"{len(would_lose)} commits from the remote history."
)
return {
"wouldLose": would_lose,
"wouldRewrite": [],
"conflicts": [],
"summary": summary,
}
except Exception as e:
logger.error(f"Danger zone simulation failed: {e}", exc_info=True)
return {
"wouldLose": [],
"wouldRewrite": [],
"conflicts": [],
"summary": f"Simulation failed: {str(e)}",
"error": str(e),
}
finally:
try:
shutil.rmtree(temp_dir, ignore_errors=True)
except Exception:
pass