"""Safely fast-forward a Git installation of this node package.""" import ipaddress import os import subprocess import sys import tempfile from pathlib import Path from urllib.parse import urlsplit PLUGIN_DIR = Path(__file__).resolve().parent.parent class UpdateError(Exception): pass def is_local_management_request(request): """Allow software management only from the local ComfyUI browser.""" peer = request.transport.get_extra_info("peername") if request.transport else None if not peer: return False try: if not ipaddress.ip_address(peer[0]).is_loopback: return False host = request.headers.get("Host", "") host_url = urlsplit(f"http://{host}") hostname = host_url.hostname if host_url.username or host_url.password or host_url.path or host_url.query or host_url.fragment: return False if hostname != "localhost" and not ipaddress.ip_address(hostname).is_loopback: return False origin = request.headers.get("Origin", "") parsed_origin = urlsplit(origin) except (ValueError, TypeError): return False return ( parsed_origin.scheme == request.scheme and parsed_origin.netloc.lower() == host.lower() and not parsed_origin.path and not parsed_origin.query and not parsed_origin.fragment ) def _git(*args, timeout=60, check=True): env = os.environ.copy() env["GIT_TERMINAL_PROMPT"] = "0" env["GCM_INTERACTIVE"] = "Never" try: result = subprocess.run( ["git", *args], cwd=PLUGIN_DIR, env=env, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout, ) except FileNotFoundError as exc: raise UpdateError("未找到 Git,请先安装 Git。") from exc except subprocess.TimeoutExpired as exc: raise UpdateError("Git 操作超时,请检查网络后重试。") from exc if check and result.returncode: detail = (result.stderr or result.stdout).strip().splitlines() raise UpdateError(detail[-1] if detail else "Git 操作失败。") return result def _install_requirements(requirements): fd, filename = tempfile.mkstemp(prefix=".o1key-requirements-", suffix=".txt", dir=PLUGIN_DIR) try: with os.fdopen(fd, "w", encoding="utf-8") as req_file: req_file.write(requirements) try: result = subprocess.run( [sys.executable, "-m", "pip", "install", "-r", filename], cwd=PLUGIN_DIR, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=600, ) except subprocess.TimeoutExpired as exc: raise UpdateError("依赖安装超时,节点代码尚未更新,请检查网络后重试。") from exc if result.returncode: raise UpdateError("依赖安装失败,节点代码尚未更新,请检查 ComfyUI 的 Python 环境后重试。") finally: Path(filename).unlink(missing_ok=True) def update_package(): """Update origin/main without discarding local changes or switching branches.""" if not (PLUGIN_DIR / ".git").exists(): raise UpdateError("当前节点包不是 Git 安装。请通过 Git 安装后再使用界面更新。") branch = _git("symbolic-ref", "--quiet", "--short", "HEAD", check=False) if branch.returncode or branch.stdout.strip() != "main": raise UpdateError("当前不在 main 分支,请手动检查分支后更新。") if _git("status", "--porcelain", "--untracked-files=no").stdout.strip(): raise UpdateError("节点包有本地修改,请先保存或处理修改后再更新。") old_commit = _git("rev-parse", "HEAD").stdout.strip() old_requirements = _git("show", "HEAD:requirements.txt", check=False).stdout _git("fetch", "origin", "main") new_commit = _git("rev-parse", "FETCH_HEAD").stdout.strip() if old_commit == new_commit: return {"updated": False, "version": old_commit[:7], "requirements_changed": False} if _git("merge-base", "--is-ancestor", "HEAD", "FETCH_HEAD", check=False).returncode: raise UpdateError("本地与 origin/main 已分叉,无法安全快进。请手动处理。") new_requirements = _git("show", "FETCH_HEAD:requirements.txt", check=False).stdout requirements_changed = old_requirements != new_requirements if requirements_changed and new_requirements.strip(): _install_requirements(new_requirements) _git("merge", "--ff-only", "FETCH_HEAD") return { "updated": True, "version": new_commit[:7], "requirements_changed": requirements_changed, }