"""Git 热更新:定期检查远端新版本,更新前先推送通知,再拉取、同步依赖并重启进程。 基于 GitPython。重启前会执行 `uv sync` 使依赖与新版本一致,失败则不重启。 """ import asyncio import logging import os import shutil import subprocess import sys from pathlib import Path import git log = logging.getLogger("updater") def _find_uv() -> str | None: for name in ("uv", "uv.exe"): path = shutil.which(name) if path: return path # 常见安装位置兜底 for path in ( Path.home() / ".local" / "bin" / "uv", Path.home() / ".cargo" / "bin" / "uv.exe", ): if path.exists(): return str(path) return None class Updater: def __init__(self, ob, cfg: dict): self.ob = ob self.interval = cfg.get("interval", 300) self.groups = cfg.get("groups", []) or [] self.friends = cfg.get("friends", []) or [] self._updating = False try: self.repo = git.Repo(".") except (git.InvalidGitRepositoryError, git.NoSuchPathError): self.repo = None def start(self): if self.repo is None: log.info("非 git 仓库,热更新已禁用") return self._task = asyncio.get_running_loop().create_task(self._loop()) async def stop(self): if hasattr(self, "_task"): self._task.cancel() async def _loop(self): log.info("热更新已启动: 每 %ds 检查一次远端", self.interval) while True: try: await asyncio.sleep(self.interval) if self.ob.connected and not self._updating: await self.check_once() except asyncio.CancelledError: break except Exception: log.exception("热更新检查出错") async def check_once(self): """检查远端;有更新则通知、拉取并重启进程。""" self._updating = True try: await asyncio.to_thread(self.repo.remotes.origin.fetch) refs = self.repo.remotes.origin.refs remote = getattr(refs, "HEAD", None) or (refs[0] if refs else None) if remote is None or remote.commit == self.repo.head.commit: return log.info("发现新版本: %s -> %s", self.repo.head.commit.hexsha[:8], remote.commit.hexsha[:8]) if self.ob.connected and (self.groups or self.friends): await self.ob.broadcast( "[更新] 检测到新版本,正在更新并重启机器人...", self.groups, self.friends) await asyncio.to_thread(self.repo.remotes.origin.pull, "--ff-only") # 同步依赖(pyproject/uv.lock 可能在新版本中有变化) uv = _find_uv() if uv is None: log.warning("未找到 uv,跳过依赖同步,直接重启") else: log.info("执行 uv sync 同步依赖...") r = await asyncio.to_thread( subprocess.run, [uv, "sync"], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=300, ) if r.returncode != 0: log.error("uv sync 失败,取消重启: %s", (r.stderr or "")[-500:]) return log.info("依赖同步完成") log.warning("更新完成,重启进程...") await asyncio.sleep(1) os.execv(sys.executable, [sys.executable] + sys.argv) finally: self._updating = False