"""把成品章节与年表推送到自建 Gitea。 - 推送凭据复用现有 ~/.ssh/id_ed25519(已验证在 Gitea 注册为 gitadmin 的 chendy-ubuntu-wsl)。 - 原始 legends 数据与存档不入库(见 .gitignore)。 - 自动直推,不设人工审核环节(按访谈决策)。 """ from __future__ import annotations import subprocess from pathlib import Path from dfannals.config import GITEA_REMOTE, PROJECT_DIR, SSH_KEY GITIGNORE = """\ # 原始数据与存档:不入库 data/ vendor/ __pycache__/ *.pyc *.log """ def _git(*args: str, check: bool = True) -> subprocess.CompletedProcess: env = { "GIT_SSH_COMMAND": f"ssh -i {SSH_KEY} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new", "GIT_TERMINAL_PROMPT": "0", } proc = subprocess.run( ["git", *args], cwd=PROJECT_DIR, env={**_base_env(), **env}, capture_output=True, text=True, check=False, # 下面是自定义错误处理,故意不用 check=True ) if check and proc.returncode != 0: raise RuntimeError(f"git {' '.join(args)} 失败:{proc.stderr.strip() or proc.stdout.strip()}") return proc def _base_env() -> dict: import os env = dict(os.environ) env.pop("GIT_DIR", None) return env def ensure_repo() -> None: """初始化仓库、写 .gitignore、设置 remote 与提交身份。""" if not (PROJECT_DIR / ".git").is_dir(): _git("init", "-b", "main") (PROJECT_DIR / ".gitignore").write_text(GITIGNORE) remotes = _git("remote").stdout.split() if "origin" in remotes: _git("remote", "set-url", "origin", GITEA_REMOTE) else: _git("remote", "add", "origin", GITEA_REMOTE) if not _git("config", "user.name", check=False).stdout.strip(): _git("config", "user.name", "DF Annals Bot") if not _git("config", "user.email", check=False).stdout.strip(): _git("config", "user.email", "df-annals@hajim1.art") def commit_and_push(message: str, paths: list[Path] | None = None) -> str: """提交并推送。返回提交哈希;无改动时返回空串。""" if paths: for p in paths: _git("add", "--", str(p.relative_to(PROJECT_DIR)) if p.is_absolute() else str(p)) else: _git("add", "-A") if not _git("status", "--porcelain").stdout.strip(): return "" _git("commit", "-m", message) head = _git("rev-parse", "--short", "HEAD").stdout.strip() _git("push", "-u", "origin", "HEAD:main") return head