import subprocess import os import shutil import stat from pathlib import Path def force_remove_all(path: str): """Erzwingt das Loeschen von Dateien und Ordnern mit chmod 0o666 fuer ALLE.""" path = Path(path) if not path.exists(): return # ✅ ZUERST: Alles auf Schreibbar setzen (auch schreibgeschuetzte .git files!) for root, dirs, files in os.walk(str(path), topdown=False): for d in dirs: try: dir_path = Path(root) / d dir_path.chmod(0o755 | stat.S_IWUSR) except Exception: pass for f in files: try: file_path = Path(root) / f file_path.chmod(0o666) # Wichtig: Alles beschreibbar fuer Loeschen! except Exception: pass # Jetzt die Root-Ordner auch beschreibbar machen try: path.chmod(0o755 | stat.S_IWUSR) except Exception: pass def set_permissions(path: str): """Setzt korrekte POSIX-Berechtigungen fuer ein Verzeichnis nach dem Klonen.""" try: # Ordner = 755 (rwxr-xr-x) os.chmod(str(path), 0o755) # Rekursiv alle Teildateien bearbeiten for root, dirs, files in os.walk(str(path)): for d in dirs: full_path = os.path.join(root, d) try: os.chmod(full_path, 0o755) except Exception: pass for f in files: full_path = os.path.join(root, f) try: # Nur les- und schreibbar fuer owner, sonst nur lesen os.chmod(full_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) except Exception: pass # Nachher auch den Hauptordner beschreiben machen os.chmod(str(path), 0o755) except Exception as e: print(f"Warnung bei Berechtigungen: {e}") def clone_repo(repo_url: str, target_dir: Path, token: str = None): """Klont ein Repository in den Zielordner.""" if not target_dir.exists(): target_dir.mkdir(parents=True) # URL mit Token (falls noetig) if token and "@" not in repo_url: repo_url = repo_url.replace("https://", f"https://{token}@") result = subprocess.run( ["git", "clone", repo_url, str(target_dir)], capture_output=True, text=True ) # ✅ BERECHTIGUNGEN NACH DEM KLOONEN SETZEN! if result.returncode == 0: set_permissions(str(target_dir)) return {"success": result.returncode == 0, "output": result.stdout, "error": result.stderr} def unsubscribe_repo(project_name: str, projects_dir: Path): """Loeschen eines abonnierten Projekts inklusive Berechtigungen.""" target_dir = projects_dir / project_name if not target_dir.exists(): return {"success": False, "error": "Projekt nicht gefunden"} try: # ✅ SCHRITT 1: Alles CHMOD 0o666 fuer LOESCHEN! force_remove_all(str(target_dir)) # ✅ SCHRITT 2: Erst jetzt.loeschen shutil.rmtree(str(target_dir)) return {"success": True, "message": f"{project_name} erfolgreich geloescht"} except Exception as e: # Fallback mit rm -rf try: subprocess.run(["rm", "-rf", str(target_dir)], check=True) return {"success": True, "message": f"{project_name} geloescht (Force)"} except Exception as e2: return {"success": False, "error": f"Konnte nicht loeschen: {e}"} def push_changes(repo_path: Path, message: str = "Update 🧠", changelog_content: str = None): """Pusht lokale Änderungen ins Repository mit optionalem Changelog-Content.""" # Optional: Changelog aktualisieren if changelog_content: changelog_path = repo_path / "CHANGELOG.md" changelog_path.write_text(changelog_content) result = subprocess.run( ["git", "add", "."], cwd=repo_path, capture_output=True, text=True ) if result.returncode != 0: return {"success": False, "error": f"git add fehlgeschlagen: {result.stderr}"} # Commit message mit optionaler Info aus Changelog commit_msg = message if changelog_content: commit_msg += "\n\nChangelog aktualisiert" result = subprocess.run( ["git", "commit", "-m", commit_msg], cwd=repo_path, capture_output=True, text=True ) if result.returncode != 0 and "nothing to commit" not in result.stderr.lower(): return {"success": False, "error": f"Commit fehlgeschlagen: {result.stderr}"} result = subprocess.run( ["git", "push"], cwd=repo_path, capture_output=True, text=True ) return {"success": result.returncode == 0, "output": result.stdout} def get_git_status(repo_path: Path) -> dict: """Prueft den Status eines Git-Repo fuer aenderungen.""" # Setze git identity if not set (fuer Push ohne manuelle Konfig) try: subprocess.run( ["git", "config", "--global", "user.name", "KI-Sparringpartner"], cwd=repo_path, capture_output=True ) subprocess.run( ["git", "config", "--global", "user.email", "ai@local.internal"], cwd=repo_path, capture_output=True ) except Exception: pass # Falls git config fehlschlaegt, versuch trotzdem push # Check fuer uncommitted changes result = subprocess.run( ["git", "status", "--porcelain"], cwd=repo_path, capture_output=True, text=True ) has_changes = bool(result.stdout.strip()) return { "has_changes": has_changes, "uncommitted_files": result.stdout.strip().split("\n") if result.stdout else [], "can_push": has_changes }