from __future__ import annotations import configparser import logging from io import StringIO from pathlib import Path from app.models import Mirror from app.services.host import host_path, run_on_host logger = logging.getLogger(__name__) MARKER = "mirror-manager" INSTEAD_SECTION = f'url "https://github.com/" managed by {MARKER}' def _gitconfig_paths() -> list[Path]: paths = [] for rel in ("root/.gitconfig", "home/.gitconfig"): p = host_path(rel) if p not in paths: paths.append(p) local = Path.home() / ".gitconfig" if local not in paths: paths.append(local) system = host_path("etc/gitconfig") if system not in paths: paths.append(system) return paths def read_current_github() -> dict: configs = {} for path in _gitconfig_paths(): if path.exists(): configs[str(path)] = path.read_text(encoding="utf-8") return configs def _parse_gitconfig(content: str) -> configparser.ConfigParser: parser = configparser.ConfigParser() parser.read_string(content if content.strip() else "[core]\n") return parser def _serialize_gitconfig(parser: configparser.ConfigParser) -> str: buf = StringIO() parser.write(buf) return buf.getvalue() def _remove_mirror_sections(parser: configparser.ConfigParser) -> None: to_remove = [] for section in parser.sections(): if not section.startswith('url "'): continue instead_of = parser.get(section, "insteadOf", fallback="") if instead_of in ("https://github.com/", "git@github.com:"): to_remove.append(section) for section in to_remove: parser.remove_section(section) def apply_github(mirror: Mirror) -> dict: meta = mirror.get_meta() instead_prefix = meta.get("instead_prefix") or mirror.url if not instead_prefix: return {"success": False, "error": "prefix میرور GitHub تعریف نشده"} instead_prefix = instead_prefix.rstrip("/") + "/" section_name = f'url "{instead_prefix}"' applied_paths = [] for path in _gitconfig_paths(): content = path.read_text(encoding="utf-8") if path.exists() else "" parser = _parse_gitconfig(content) _remove_mirror_sections(parser) if not parser.has_section(section_name): parser.add_section(section_name) parser.set(section_name, "insteadOf", "https://github.com/") path.parent.mkdir(parents=True, exist_ok=True) path.write_text(_serialize_gitconfig(parser), encoding="utf-8") applied_paths.append(str(path)) test = run_on_host( ["git", "ls-remote", "https://github.com/octocat/Hello-World.git", "HEAD"], timeout=60, ) return { "success": test.returncode == 0, "message": f"GitHub mirror اعمال شد: {mirror.name}", "paths": applied_paths, "test_output": (test.stdout or test.stderr)[:300] if test.returncode != 0 else "git ls-remote موفق", "error": test.stderr[:300] if test.returncode != 0 else None, } def remove_github_config() -> None: for path in _gitconfig_paths(): if not path.exists(): continue parser = _parse_gitconfig(path.read_text(encoding="utf-8")) _remove_mirror_sections(parser) path.write_text(_serialize_gitconfig(parser), encoding="utf-8")