mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-15 17:20:01 +08:00
✨ feat(update): 移除资源管理器,重构更新逻辑,支持通过ZhenxunRepoManager进行资源和Web UI的更新
This commit is contained in:
@@ -1,101 +0,0 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
from zhenxun.configs.path_config import FONT_PATH, TEMP_PATH
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.github_utils import GithubUtils
|
||||
from zhenxun.utils.http_utils import AsyncHttpx
|
||||
from zhenxun.utils.repo_utils import AliyunRepoManager, GithubRepoManager
|
||||
from zhenxun.utils.repo_utils.utils import clean_git
|
||||
|
||||
LOG_COMMAND = "ResourceManager"
|
||||
|
||||
|
||||
class DownloadResourceException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ResourceManager:
|
||||
GITHUB_URL = "https://github.com/zhenxun-org/zhenxun-bot-resources/tree/main"
|
||||
|
||||
RESOURCE_PATH = Path() / "resources"
|
||||
|
||||
TMP_PATH = TEMP_PATH / "_resource_tmp"
|
||||
|
||||
ZIP_FILE = TMP_PATH / "resources.zip"
|
||||
|
||||
UNZIP_PATH = None
|
||||
|
||||
@classmethod
|
||||
async def init_resources(
|
||||
cls, force: bool = False, is_zip: bool = False, git_source: str = "ali"
|
||||
):
|
||||
if (FONT_PATH.exists() and os.listdir(FONT_PATH)) and not force:
|
||||
return
|
||||
if is_zip:
|
||||
if cls.TMP_PATH.exists():
|
||||
logger.debug(
|
||||
"resources临时文件夹已存在,移除resources临时文件夹", LOG_COMMAND
|
||||
)
|
||||
await clean_git(cls.TMP_PATH)
|
||||
shutil.rmtree(cls.TMP_PATH, ignore_errors=True)
|
||||
cls.TMP_PATH.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
await cls.__download_resources()
|
||||
cls.file_handle()
|
||||
except Exception as e:
|
||||
logger.error("获取resources资源包失败", LOG_COMMAND, e=e)
|
||||
else:
|
||||
if git_source == "ali":
|
||||
await AliyunRepoManager.update(cls.GITHUB_URL, cls.RESOURCE_PATH)
|
||||
else:
|
||||
await GithubRepoManager.update(cls.GITHUB_URL, cls.RESOURCE_PATH)
|
||||
cls.UNZIP_PATH = cls.TMP_PATH / "resources"
|
||||
cls.file_handle()
|
||||
if cls.TMP_PATH.exists():
|
||||
logger.debug("移除resources临时文件夹", LOG_COMMAND)
|
||||
await clean_git(cls.TMP_PATH)
|
||||
shutil.rmtree(cls.TMP_PATH)
|
||||
|
||||
@classmethod
|
||||
def file_handle(cls):
|
||||
if not cls.UNZIP_PATH:
|
||||
return
|
||||
cls.__recursive_folder(cls.UNZIP_PATH, ".")
|
||||
|
||||
@classmethod
|
||||
def __recursive_folder(cls, dir: Path, parent_path: str):
|
||||
for file in dir.iterdir():
|
||||
if file.is_dir():
|
||||
cls.__recursive_folder(file, f"{parent_path}/{file.name}")
|
||||
else:
|
||||
res_file = Path(parent_path) / file.name
|
||||
if res_file.exists():
|
||||
res_file.unlink()
|
||||
res_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
file.rename(res_file)
|
||||
|
||||
@classmethod
|
||||
async def __download_resources(cls):
|
||||
"""获取resources文件夹"""
|
||||
repo_info = GithubUtils.parse_github_url(cls.GITHUB_URL)
|
||||
url = await repo_info.get_archive_download_urls()
|
||||
logger.debug("开始下载resources资源包...", LOG_COMMAND)
|
||||
if not await AsyncHttpx.download_file(url, cls.ZIP_FILE, stream=True):
|
||||
logger.error(
|
||||
"下载resources资源包失败,请尝试重启重新下载或前往 "
|
||||
"https://github.com/zhenxun-org/zhenxun-bot-resources 手动下载..."
|
||||
)
|
||||
raise DownloadResourceException("下载resources资源包失败...")
|
||||
logger.debug("下载resources资源文件压缩包完成...", LOG_COMMAND)
|
||||
tf = zipfile.ZipFile(cls.ZIP_FILE)
|
||||
tf.extractall(cls.TMP_PATH)
|
||||
logger.debug("解压文件压缩包完成...", LOG_COMMAND)
|
||||
download_file_path = cls.TMP_PATH / next(
|
||||
x for x in os.listdir(cls.TMP_PATH) if (cls.TMP_PATH / x).is_dir()
|
||||
)
|
||||
cls.UNZIP_PATH = download_file_path / "resources"
|
||||
if tf:
|
||||
tf.close()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,9 +24,6 @@ from .models import (
|
||||
RepoFileInfo,
|
||||
RepoType,
|
||||
RepoUpdateResult,
|
||||
SubmoduleConfig,
|
||||
SubmoduleInfo,
|
||||
SubmoduleUpdateResult,
|
||||
)
|
||||
from .utils import check_git, filter_files, glob_to_regex, run_git_command
|
||||
|
||||
@@ -56,9 +53,6 @@ __all__ = [
|
||||
"RepoType",
|
||||
"RepoUpdateError",
|
||||
"RepoUpdateResult",
|
||||
"SubmoduleConfig",
|
||||
"SubmoduleInfo",
|
||||
"SubmoduleUpdateResult",
|
||||
"check_git",
|
||||
"filter_files",
|
||||
"glob_to_regex",
|
||||
|
||||
@@ -283,10 +283,11 @@ class BaseRepoManager(ABC):
|
||||
return result
|
||||
|
||||
# 如果目录存在,检查是否是Git仓库
|
||||
success, _, _ = await run_git_command(
|
||||
"rev-parse --is-inside-work-tree", cwd=local_path
|
||||
)
|
||||
if not success:
|
||||
# 首先检查目录本身是否有.git文件夹
|
||||
git_dir = local_path / ".git"
|
||||
is_git_repo = git_dir.exists() and git_dir.is_dir()
|
||||
|
||||
if not is_git_repo:
|
||||
# 如果不是Git仓库,尝试初始化它
|
||||
logger.info(f"目录 {local_path} 不是Git仓库,尝试初始化", LOG_COMMAND)
|
||||
init_success, _, init_stderr = await run_git_command(
|
||||
@@ -338,7 +339,7 @@ class BaseRepoManager(ABC):
|
||||
)
|
||||
|
||||
# 获取远程更新
|
||||
logger.info("获取远程更新", LOG_COMMAND)
|
||||
logger.info(f"获取远程更新: {repo_url}", LOG_COMMAND)
|
||||
success, _, stderr = await run_git_command("fetch origin", cwd=local_path)
|
||||
if not success:
|
||||
return RepoUpdateResult(
|
||||
@@ -373,7 +374,7 @@ class BaseRepoManager(ABC):
|
||||
)
|
||||
|
||||
# 拉取最新代码
|
||||
logger.info("拉取最新代码", LOG_COMMAND)
|
||||
logger.info(f"拉取最新代码: {repo_url}", LOG_COMMAND)
|
||||
pull_cmd = f"pull origin {branch}"
|
||||
if force:
|
||||
pull_cmd = f"fetch --all && git reset --hard origin/{branch}"
|
||||
|
||||
@@ -29,11 +29,7 @@ from .models import (
|
||||
RepoFileInfo,
|
||||
RepoType,
|
||||
RepoUpdateResult,
|
||||
SubmoduleConfig,
|
||||
SubmoduleInfo,
|
||||
SubmoduleUpdateResult,
|
||||
)
|
||||
from .submodule_manager import SubmoduleManager
|
||||
|
||||
|
||||
class GithubManager(BaseRepoManager):
|
||||
@@ -47,7 +43,6 @@ class GithubManager(BaseRepoManager):
|
||||
config: 配置,如果为None则使用默认配置
|
||||
"""
|
||||
super().__init__(config)
|
||||
self.submodule_manager = SubmoduleManager(self)
|
||||
|
||||
async def update_repo(
|
||||
self,
|
||||
@@ -529,158 +524,3 @@ class GithubManager(BaseRepoManager):
|
||||
raise RepoDownloadError("下载文件失败")
|
||||
|
||||
raise RepoDownloadError("下载文件失败: 超过最大重试次数")
|
||||
|
||||
# 子模块相关方法
|
||||
async def init_submodules(
|
||||
self,
|
||||
main_repo_path: Path,
|
||||
submodule_configs: list[SubmoduleConfig],
|
||||
) -> bool:
|
||||
"""
|
||||
初始化子模块
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
submodule_configs: 子模块配置列表
|
||||
|
||||
返回:
|
||||
bool: 是否成功
|
||||
"""
|
||||
return await self.submodule_manager.init_submodules(
|
||||
main_repo_path, submodule_configs
|
||||
)
|
||||
|
||||
async def update_submodules(
|
||||
self,
|
||||
main_repo_path: Path,
|
||||
submodule_configs: list[SubmoduleConfig],
|
||||
) -> list[SubmoduleUpdateResult]:
|
||||
"""
|
||||
更新子模块
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
submodule_configs: 子模块配置列表
|
||||
|
||||
返回:
|
||||
list[SubmoduleUpdateResult]: 更新结果列表
|
||||
"""
|
||||
return await self.submodule_manager.update_submodules(
|
||||
main_repo_path, submodule_configs
|
||||
)
|
||||
|
||||
async def get_submodule_info(
|
||||
self,
|
||||
main_repo_path: Path,
|
||||
submodule_configs: list[SubmoduleConfig],
|
||||
) -> list[SubmoduleInfo]:
|
||||
"""
|
||||
获取子模块信息
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
submodule_configs: 子模块配置列表
|
||||
|
||||
返回:
|
||||
list[SubmoduleInfo]: 子模块信息列表
|
||||
"""
|
||||
return await self.submodule_manager.get_submodule_info(
|
||||
main_repo_path, submodule_configs
|
||||
)
|
||||
|
||||
def save_submodule_configs(
|
||||
self,
|
||||
main_repo_path: Path,
|
||||
submodule_configs: list[SubmoduleConfig],
|
||||
) -> bool:
|
||||
"""
|
||||
保存子模块配置到文件
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
submodule_configs: 子模块配置列表
|
||||
|
||||
返回:
|
||||
bool: 是否成功
|
||||
"""
|
||||
return self.submodule_manager.save_submodule_configs(
|
||||
main_repo_path, submodule_configs
|
||||
)
|
||||
|
||||
async def load_submodule_configs(
|
||||
self, main_repo_path: Path
|
||||
) -> list[SubmoduleConfig]:
|
||||
"""
|
||||
从文件加载子模块配置
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
|
||||
返回:
|
||||
list[SubmoduleConfig]: 子模块配置列表
|
||||
"""
|
||||
return await self.submodule_manager.load_submodule_configs(main_repo_path)
|
||||
|
||||
async def update_with_submodules(
|
||||
self,
|
||||
repo_url: str,
|
||||
local_path: Path,
|
||||
branch: str = "main",
|
||||
submodule_configs: list[SubmoduleConfig] | None = None,
|
||||
use_git: bool = True,
|
||||
force: bool = False,
|
||||
include_patterns: list[str] | None = None,
|
||||
exclude_patterns: list[str] | None = None,
|
||||
) -> RepoUpdateResult:
|
||||
"""
|
||||
更新仓库并处理子模块
|
||||
|
||||
参数:
|
||||
repo_url: 仓库URL,格式为 https://github.com/owner/repo
|
||||
local_path: 本地保存路径
|
||||
branch: 分支名称
|
||||
submodule_configs: 子模块配置列表
|
||||
use_git: 是否使用Git命令更新
|
||||
force: 是否强制更新
|
||||
include_patterns: 包含的文件模式列表
|
||||
exclude_patterns: 排除的文件模式列表
|
||||
|
||||
返回:
|
||||
RepoUpdateResult: 更新结果
|
||||
"""
|
||||
# 更新主仓库
|
||||
result = await self.update(
|
||||
repo_url,
|
||||
local_path,
|
||||
branch,
|
||||
use_git,
|
||||
force,
|
||||
include_patterns,
|
||||
exclude_patterns,
|
||||
)
|
||||
|
||||
# 如果没有子模块配置,直接返回结果
|
||||
if not submodule_configs:
|
||||
return result
|
||||
|
||||
# 处理子模块
|
||||
try:
|
||||
submodule_results = await self.update_submodules(
|
||||
local_path, submodule_configs
|
||||
)
|
||||
result.submodule_results = submodule_results
|
||||
|
||||
# 检查子模块更新是否成功
|
||||
failed_submodules = [r for r in submodule_results if not r.success]
|
||||
if failed_submodules:
|
||||
logger.warning(
|
||||
"部分子模块更新失败:"
|
||||
f" {[r.submodule_name for r in failed_submodules]}",
|
||||
LOG_COMMAND,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理子模块时发生错误: {e}", LOG_COMMAND)
|
||||
result.error_message += f"; 子模块处理失败: {e}"
|
||||
|
||||
return result
|
||||
|
||||
@@ -15,62 +15,6 @@ class RepoType(str, Enum):
|
||||
ALIYUN = "aliyun"
|
||||
|
||||
|
||||
@dataclass
|
||||
class SubmoduleConfig:
|
||||
"""子模块配置"""
|
||||
|
||||
# 子模块名称
|
||||
name: str
|
||||
# 子模块路径(相对于主仓库)
|
||||
path: str
|
||||
# 子模块仓库URL
|
||||
repo_url: str
|
||||
# 分支名称
|
||||
branch: str = "main"
|
||||
# 是否启用
|
||||
enabled: bool = True
|
||||
# 包含的文件模式列表
|
||||
include_patterns: list[str] | None = None
|
||||
# 排除的文件模式列表
|
||||
exclude_patterns: list[str] | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SubmoduleInfo:
|
||||
"""子模块信息"""
|
||||
|
||||
# 子模块配置
|
||||
config: SubmoduleConfig
|
||||
# 当前版本
|
||||
current_version: str = ""
|
||||
# 最新版本
|
||||
latest_version: str = ""
|
||||
# 最后更新时间
|
||||
last_update: datetime | None = None
|
||||
# 更新状态
|
||||
update_status: str = "unknown" # unknown, up_to_date, outdated, error
|
||||
|
||||
|
||||
@dataclass
|
||||
class SubmoduleUpdateResult:
|
||||
"""子模块更新结果"""
|
||||
|
||||
# 子模块名称
|
||||
submodule_name: str
|
||||
# 子模块路径
|
||||
submodule_path: str
|
||||
# 旧版本
|
||||
old_version: str
|
||||
# 新版本
|
||||
new_version: str
|
||||
# 是否成功
|
||||
success: bool = False
|
||||
# 错误消息
|
||||
error_message: str = ""
|
||||
# 变更的文件列表
|
||||
changed_files: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RepoFileInfo:
|
||||
"""仓库文件信息"""
|
||||
@@ -123,8 +67,6 @@ class RepoUpdateResult:
|
||||
error_message: str = ""
|
||||
# 变更的文件列表
|
||||
changed_files: list[str] = field(default_factory=list)
|
||||
# 子模块更新结果
|
||||
submodule_results: list[SubmoduleUpdateResult] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -1,408 +0,0 @@
|
||||
"""
|
||||
子模块管理工具
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
from .config import LOG_COMMAND
|
||||
from .github_manager import GithubManager
|
||||
from .models import SubmoduleConfig, SubmoduleInfo, SubmoduleUpdateResult
|
||||
from .utils import run_git_command
|
||||
|
||||
|
||||
class SubmoduleManager:
|
||||
"""子模块管理器"""
|
||||
|
||||
def __init__(self, github_manager: GithubManager):
|
||||
"""
|
||||
初始化子模块管理器
|
||||
|
||||
参数:
|
||||
github_manager: GitHub管理器实例
|
||||
"""
|
||||
self.github_manager = github_manager
|
||||
|
||||
async def init_submodules(
|
||||
self, main_repo_path: Path, submodule_configs: list[SubmoduleConfig]
|
||||
) -> bool:
|
||||
"""
|
||||
初始化子模块
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
submodule_configs: 子模块配置列表
|
||||
|
||||
返回:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
# 检查是否在Git仓库中
|
||||
success, stdout, stderr = await run_git_command("status", main_repo_path)
|
||||
if not success:
|
||||
logger.error(f"路径 {main_repo_path} 不是有效的Git仓库", LOG_COMMAND)
|
||||
return False
|
||||
|
||||
# 初始化每个子模块
|
||||
for config in submodule_configs:
|
||||
if not config.enabled:
|
||||
continue
|
||||
|
||||
await self._init_single_submodule(main_repo_path, config)
|
||||
|
||||
# 更新子模块
|
||||
await self._update_submodules(main_repo_path)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"初始化子模块失败: {e}", LOG_COMMAND)
|
||||
return False
|
||||
|
||||
async def _init_single_submodule(
|
||||
self, main_repo_path: Path, config: SubmoduleConfig
|
||||
) -> bool:
|
||||
"""
|
||||
初始化单个子模块
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
config: 子模块配置
|
||||
|
||||
返回:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
submodule_path = main_repo_path / config.path
|
||||
|
||||
# 检查子模块是否已存在
|
||||
if submodule_path.exists() and (submodule_path / ".git").exists():
|
||||
logger.info(f"子模块 {config.name} 已存在,跳过初始化", LOG_COMMAND)
|
||||
return True
|
||||
|
||||
# 添加子模块
|
||||
success, stdout, stderr = await run_git_command(
|
||||
f"submodule add -b {config.branch} {config.repo_url} {config.path}",
|
||||
main_repo_path,
|
||||
)
|
||||
|
||||
if not success:
|
||||
logger.error(f"添加子模块 {config.name} 失败: {stderr}", LOG_COMMAND)
|
||||
return False
|
||||
|
||||
logger.info(f"成功添加子模块 {config.name}", LOG_COMMAND)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"初始化子模块 {config.name} 失败: {e}", LOG_COMMAND)
|
||||
return False
|
||||
|
||||
async def _update_submodules(self, main_repo_path: Path) -> bool:
|
||||
"""
|
||||
更新所有子模块
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
|
||||
返回:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
# 更新子模块
|
||||
success, stdout, stderr = await run_git_command(
|
||||
"submodule update --init --recursive", main_repo_path
|
||||
)
|
||||
|
||||
if not success:
|
||||
logger.error(f"更新子模块失败: {stderr}", LOG_COMMAND)
|
||||
return False
|
||||
|
||||
logger.info("成功更新所有子模块", LOG_COMMAND)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"更新子模块失败: {e}", LOG_COMMAND)
|
||||
return False
|
||||
|
||||
async def update_submodules(
|
||||
self, main_repo_path: Path, submodule_configs: list[SubmoduleConfig]
|
||||
) -> list[SubmoduleUpdateResult]:
|
||||
"""
|
||||
更新子模块
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
submodule_configs: 子模块配置列表
|
||||
|
||||
返回:
|
||||
List[SubmoduleUpdateResult]: 更新结果列表
|
||||
"""
|
||||
results = []
|
||||
|
||||
for config in submodule_configs:
|
||||
if not config.enabled:
|
||||
continue
|
||||
|
||||
result = await self._update_single_submodule(main_repo_path, config)
|
||||
results.append(result)
|
||||
|
||||
return results
|
||||
|
||||
async def _update_single_submodule(
|
||||
self, main_repo_path: Path, config: SubmoduleConfig
|
||||
) -> SubmoduleUpdateResult:
|
||||
"""
|
||||
更新单个子模块
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
config: 子模块配置
|
||||
|
||||
返回:
|
||||
SubmoduleUpdateResult: 更新结果
|
||||
"""
|
||||
result = SubmoduleUpdateResult(
|
||||
submodule_name=config.name,
|
||||
submodule_path=config.path,
|
||||
old_version="",
|
||||
new_version="",
|
||||
)
|
||||
|
||||
try:
|
||||
submodule_path = main_repo_path / config.path
|
||||
|
||||
# 检查子模块是否存在
|
||||
if not submodule_path.exists():
|
||||
result.error_message = f"子模块路径不存在: {submodule_path}"
|
||||
return result
|
||||
|
||||
# 获取当前版本
|
||||
success, stdout, stderr = await run_git_command(
|
||||
"rev-parse HEAD", submodule_path
|
||||
)
|
||||
|
||||
if not success:
|
||||
result.error_message = f"获取当前版本失败: {stderr}"
|
||||
return result
|
||||
|
||||
old_version = stdout.strip()
|
||||
result.old_version = old_version
|
||||
|
||||
# 获取远程最新版本
|
||||
success, stdout, stderr = await run_git_command(
|
||||
f"ls-remote origin {config.branch}", submodule_path
|
||||
)
|
||||
|
||||
if not success:
|
||||
result.error_message = f"获取远程版本失败: {stderr}"
|
||||
return result
|
||||
|
||||
# 解析最新版本
|
||||
lines = stdout.strip().split("\n")
|
||||
if not lines or not lines[0]:
|
||||
result.error_message = "无法获取远程版本信息"
|
||||
return result
|
||||
|
||||
latest_version = lines[0].split("\t")[0]
|
||||
result.new_version = latest_version
|
||||
|
||||
# 检查是否需要更新
|
||||
if old_version == latest_version:
|
||||
result.success = True
|
||||
logger.info(f"子模块 {config.name} 已是最新版本", LOG_COMMAND)
|
||||
return result
|
||||
|
||||
# 更新子模块
|
||||
success, stdout, stderr = await run_git_command(
|
||||
f"pull origin {config.branch}", submodule_path
|
||||
)
|
||||
|
||||
if not success:
|
||||
result.error_message = f"更新子模块失败: {stderr}"
|
||||
return result
|
||||
|
||||
# 更新主仓库中的子模块引用
|
||||
success, stdout, stderr = await run_git_command(
|
||||
f"add {config.path}", main_repo_path
|
||||
)
|
||||
|
||||
if not success:
|
||||
result.error_message = f"更新主仓库引用失败: {stderr}"
|
||||
return result
|
||||
|
||||
result.success = True
|
||||
logger.info(
|
||||
f"成功更新子模块 {config.name}: {old_version} -> {latest_version}",
|
||||
LOG_COMMAND,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
result.error_message = f"更新子模块时发生错误: {e}"
|
||||
logger.error(f"更新子模块 {config.name} 失败: {e}", LOG_COMMAND)
|
||||
|
||||
return result
|
||||
|
||||
async def get_submodule_info(
|
||||
self, main_repo_path: Path, submodule_configs: list[SubmoduleConfig]
|
||||
) -> list[SubmoduleInfo]:
|
||||
"""
|
||||
获取子模块信息
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
submodule_configs: 子模块配置列表
|
||||
|
||||
返回:
|
||||
List[SubmoduleInfo]: 子模块信息列表
|
||||
"""
|
||||
submodule_infos = []
|
||||
|
||||
for config in submodule_configs:
|
||||
if not config.enabled:
|
||||
continue
|
||||
|
||||
info = await self._get_single_submodule_info(main_repo_path, config)
|
||||
submodule_infos.append(info)
|
||||
|
||||
return submodule_infos
|
||||
|
||||
async def _get_single_submodule_info(
|
||||
self, main_repo_path: Path, config: SubmoduleConfig
|
||||
) -> SubmoduleInfo:
|
||||
"""
|
||||
获取单个子模块信息
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
config: 子模块配置
|
||||
|
||||
返回:
|
||||
SubmoduleInfo: 子模块信息
|
||||
"""
|
||||
info = SubmoduleInfo(config=config)
|
||||
|
||||
try:
|
||||
submodule_path = main_repo_path / config.path
|
||||
|
||||
if not submodule_path.exists():
|
||||
info.update_status = "error"
|
||||
return info
|
||||
|
||||
# 获取当前版本
|
||||
success, stdout, stderr = await run_git_command(
|
||||
"rev-parse HEAD", submodule_path
|
||||
)
|
||||
|
||||
if success:
|
||||
info.current_version = stdout.strip()
|
||||
|
||||
# 获取远程最新版本
|
||||
success, stdout, stderr = await run_git_command(
|
||||
f"ls-remote origin {config.branch}", submodule_path
|
||||
)
|
||||
|
||||
if success and stdout.strip():
|
||||
lines = stdout.strip().split("\n")
|
||||
if lines and lines[0]:
|
||||
info.latest_version = lines[0].split("\t")[0]
|
||||
|
||||
# 确定更新状态
|
||||
if info.current_version and info.latest_version:
|
||||
if info.current_version == info.latest_version:
|
||||
info.update_status = "up_to_date"
|
||||
else:
|
||||
info.update_status = "outdated"
|
||||
else:
|
||||
info.update_status = "unknown"
|
||||
|
||||
except Exception as e:
|
||||
info.update_status = "error"
|
||||
logger.error(f"获取子模块 {config.name} 信息失败: {e}", LOG_COMMAND)
|
||||
|
||||
return info
|
||||
|
||||
def save_submodule_configs(
|
||||
self, main_repo_path: Path, submodule_configs: list[SubmoduleConfig]
|
||||
) -> bool:
|
||||
"""
|
||||
保存子模块配置到文件
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
submodule_configs: 子模块配置列表
|
||||
|
||||
返回:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
config_file = main_repo_path / ".submodules.json"
|
||||
|
||||
# 转换为字典格式
|
||||
configs_dict = []
|
||||
for config in submodule_configs:
|
||||
config_dict = {
|
||||
"name": config.name,
|
||||
"path": config.path,
|
||||
"repo_url": config.repo_url,
|
||||
"branch": config.branch,
|
||||
"enabled": config.enabled,
|
||||
"include_patterns": config.include_patterns,
|
||||
"exclude_patterns": config.exclude_patterns,
|
||||
}
|
||||
configs_dict.append(config_dict)
|
||||
|
||||
# 保存到文件
|
||||
with open(config_file, "w", encoding="utf-8") as f:
|
||||
json.dump(configs_dict, f, indent=2, ensure_ascii=False)
|
||||
|
||||
logger.info(f"子模块配置已保存到 {config_file}", LOG_COMMAND)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"保存子模块配置失败: {e}", LOG_COMMAND)
|
||||
return False
|
||||
|
||||
def load_submodule_configs(self, main_repo_path: Path) -> list[SubmoduleConfig]:
|
||||
"""
|
||||
从文件加载子模块配置
|
||||
|
||||
参数:
|
||||
main_repo_path: 主仓库路径
|
||||
|
||||
返回:
|
||||
List[SubmoduleConfig]: 子模块配置列表
|
||||
"""
|
||||
try:
|
||||
config_file = main_repo_path / ".submodules.json"
|
||||
|
||||
if not config_file.exists():
|
||||
logger.warning(f"子模块配置文件不存在: {config_file}", LOG_COMMAND)
|
||||
return []
|
||||
|
||||
with open(config_file, encoding="utf-8") as f:
|
||||
configs_dict = json.load(f)
|
||||
|
||||
# 转换为SubmoduleConfig对象
|
||||
configs = []
|
||||
for config_dict in configs_dict:
|
||||
config = SubmoduleConfig(
|
||||
name=config_dict["name"],
|
||||
path=config_dict["path"],
|
||||
repo_url=config_dict["repo_url"],
|
||||
branch=config_dict.get("branch", "main"),
|
||||
enabled=config_dict.get("enabled", True),
|
||||
include_patterns=config_dict.get("include_patterns"),
|
||||
exclude_patterns=config_dict.get("exclude_patterns"),
|
||||
)
|
||||
configs.append(config)
|
||||
|
||||
logger.info(
|
||||
f"从 {config_file} 加载了 {len(configs)} 个子模块配置", LOG_COMMAND
|
||||
)
|
||||
return configs
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"加载子模块配置失败: {e}", LOG_COMMAND)
|
||||
return []
|
||||
@@ -1,210 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
GitHub子模块快速设置脚本
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.repo_utils import (
|
||||
GithubRepoManager,
|
||||
SubmoduleConfig,
|
||||
)
|
||||
|
||||
|
||||
def create_sample_configs():
|
||||
"""创建示例子模块配置"""
|
||||
return [
|
||||
SubmoduleConfig(
|
||||
name="frontend-ui",
|
||||
path="frontend/ui",
|
||||
repo_url="https://github.com/your-org/frontend-ui",
|
||||
branch="main",
|
||||
enabled=True,
|
||||
include_patterns=["*.js", "*.css", "*.html", "*.vue", "*.ts"],
|
||||
exclude_patterns=["node_modules/*", "*.log", "dist/*", "coverage/*"],
|
||||
),
|
||||
SubmoduleConfig(
|
||||
name="backend-api",
|
||||
path="backend/api",
|
||||
repo_url="https://github.com/your-org/backend-api",
|
||||
branch="develop",
|
||||
enabled=True,
|
||||
include_patterns=["*.py", "*.json", "requirements.txt", "*.yml"],
|
||||
exclude_patterns=["__pycache__/*", "*.pyc", "venv/*", ".pytest_cache/*"],
|
||||
),
|
||||
SubmoduleConfig(
|
||||
name="shared-lib",
|
||||
path="libs/shared",
|
||||
repo_url="https://github.com/your-org/shared-lib",
|
||||
branch="main",
|
||||
enabled=True,
|
||||
include_patterns=["*.py", "*.js", "*.ts", "*.json"],
|
||||
exclude_patterns=["tests/*", "docs/*", "examples/*"],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def setup_submodules(project_path: str, configs: list[SubmoduleConfig]):
|
||||
"""设置子模块"""
|
||||
main_repo_path = Path(project_path)
|
||||
|
||||
logger.info(f"正在为项目 {project_path} 设置子模块...")
|
||||
|
||||
# 检查路径是否存在
|
||||
if not main_repo_path.exists():
|
||||
logger.info(f"错误: 项目路径 {project_path} 不存在")
|
||||
return False
|
||||
|
||||
# 检查是否是Git仓库
|
||||
git_dir = main_repo_path / ".git"
|
||||
if not git_dir.exists():
|
||||
logger.info(f"错误: {project_path} 不是Git仓库")
|
||||
logger.info("请先执行: git init")
|
||||
return False
|
||||
|
||||
# 初始化子模块
|
||||
logger.info("正在初始化子模块...")
|
||||
success = await GithubRepoManager.init_submodules(main_repo_path, configs)
|
||||
|
||||
if not success:
|
||||
logger.info("子模块初始化失败!")
|
||||
return False
|
||||
|
||||
# 保存配置
|
||||
logger.info("正在保存子模块配置...")
|
||||
await GithubRepoManager.save_submodule_configs(main_repo_path, configs)
|
||||
|
||||
logger.info("✓ 子模块设置完成!")
|
||||
logger.info(f"配置文件已保存到: {main_repo_path / '.submodules.json'}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def update_submodules(project_path: str):
|
||||
"""更新子模块"""
|
||||
main_repo_path = Path(project_path)
|
||||
|
||||
logger.info(f"正在更新项目 {project_path} 的子模块...")
|
||||
|
||||
# 加载配置
|
||||
configs = await GithubRepoManager.load_submodule_configs(main_repo_path)
|
||||
|
||||
if not configs:
|
||||
logger.info("未找到子模块配置")
|
||||
return False
|
||||
|
||||
logger.info(f"找到 {len(configs)} 个子模块配置")
|
||||
|
||||
# 获取子模块信息
|
||||
infos = await GithubRepoManager.get_submodule_info(main_repo_path, configs)
|
||||
|
||||
logger.info("\n子模块状态:")
|
||||
for info in infos:
|
||||
status_icon = (
|
||||
"✓"
|
||||
if info.update_status == "up_to_date"
|
||||
else "⚠"
|
||||
if info.update_status == "outdated"
|
||||
else "✗"
|
||||
)
|
||||
logger.info(
|
||||
f"{status_icon} {info.config.name}"
|
||||
f"({info.config.path}) - {info.update_status}"
|
||||
)
|
||||
|
||||
# 更新子模块
|
||||
logger.info("\n正在更新子模块...")
|
||||
results = await GithubRepoManager.update_submodules(main_repo_path, configs)
|
||||
|
||||
success_count = 0
|
||||
for result in results:
|
||||
if result.success:
|
||||
success_count += 1
|
||||
if result.old_version != result.new_version:
|
||||
logger.info(f" ✓ {result.submodule_name} 已更新")
|
||||
else:
|
||||
logger.info(f" ✓ {result.submodule_name} 已是最新版本")
|
||||
else:
|
||||
logger.info(f" ✗ {result.submodule_name} 更新失败: {result.error_message}")
|
||||
|
||||
logger.info(f"\n更新完成: {success_count}/{len(results)} 个子模块更新成功")
|
||||
return success_count == len(results)
|
||||
|
||||
|
||||
async def show_submodule_info(project_path: str):
|
||||
"""显示子模块信息"""
|
||||
main_repo_path = Path(project_path)
|
||||
|
||||
logger.info(f"项目 {project_path} 的子模块信息:")
|
||||
|
||||
# 加载配置
|
||||
configs = await GithubRepoManager.load_submodule_configs(main_repo_path)
|
||||
|
||||
if not configs:
|
||||
logger.info("未找到子模块配置")
|
||||
return
|
||||
|
||||
# 获取详细信息
|
||||
infos = await GithubRepoManager.get_submodule_info(main_repo_path, configs)
|
||||
|
||||
for info in infos:
|
||||
logger.info(f"\n子模块: {info.config.name}")
|
||||
logger.info(f" 路径: {info.config.path}")
|
||||
logger.info(f" 仓库: {info.config.repo_url}")
|
||||
logger.info(f" 分支: {info.config.branch}")
|
||||
logger.info(f" 状态: {info.update_status}")
|
||||
logger.info(f" 启用: {info.config.enabled}")
|
||||
|
||||
if info.current_version:
|
||||
logger.info(f" 当前版本: {info.current_version[:8]}")
|
||||
if info.latest_version:
|
||||
logger.info(f" 最新版本: {info.latest_version[:8]}")
|
||||
|
||||
if info.config.include_patterns:
|
||||
logger.info(f" 包含文件: {', '.join(info.config.include_patterns)}")
|
||||
if info.config.exclude_patterns:
|
||||
logger.info(f" 排除文件: {', '.join(info.config.exclude_patterns)}")
|
||||
|
||||
|
||||
def print_info_usage():
|
||||
"""打印使用说明"""
|
||||
logger.info("GitHub子模块管理工具")
|
||||
logger.info("用法:")
|
||||
logger.info(" python submodule_setup.py setup <项目路径>")
|
||||
logger.info(" python submodule_setup.py update <项目路径>")
|
||||
logger.info(" python submodule_setup.py info <项目路径>")
|
||||
logger.info("示例:")
|
||||
logger.info(" python submodule_setup.py setup ./my_project")
|
||||
logger.info(" python submodule_setup.py update ./my_project")
|
||||
logger.info(" python submodule_setup.py info ./my_project")
|
||||
|
||||
|
||||
async def main():
|
||||
"""主函数"""
|
||||
if len(sys.argv) < 3:
|
||||
print_info_usage()
|
||||
return
|
||||
|
||||
command = sys.argv[1]
|
||||
project_path = sys.argv[2]
|
||||
|
||||
if command == "setup":
|
||||
configs = create_sample_configs()
|
||||
await setup_submodules(project_path, configs)
|
||||
|
||||
elif command == "update":
|
||||
await update_submodules(project_path)
|
||||
|
||||
elif command == "info":
|
||||
await show_submodule_info(project_path)
|
||||
|
||||
else:
|
||||
logger.info(f"未知命令: {command}")
|
||||
print_info_usage()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -57,11 +57,13 @@ async def run_git_command(
|
||||
"""
|
||||
try:
|
||||
full_command = f"git {command}"
|
||||
# 将Path对象转换为字符串
|
||||
cwd_str = str(cwd) if cwd else None
|
||||
process = await asyncio.create_subprocess_shell(
|
||||
full_command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=cwd,
|
||||
cwd=cwd_str,
|
||||
)
|
||||
stdout_bytes, stderr_bytes = await process.communicate()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user