增强阿里云和GitHub的文件管理功能,新增Git不可用异常处理,优化稀疏检出逻辑,提升代码可读性和稳定性。

This commit is contained in:
HibiKier 2025-08-28 11:05:19 +08:00
parent f3ff5a3404
commit b5417d1141
5 changed files with 83 additions and 44 deletions

View File

@ -276,6 +276,7 @@ class StoreManager:
else: else:
files = [RepoFileInfo(path=f"{replace_module_path}.py", is_dir=False)] files = [RepoFileInfo(path=f"{replace_module_path}.py", is_dir=False)]
local_path = BASE_PATH / "plugins" if is_external else BASE_PATH local_path = BASE_PATH / "plugins" if is_external else BASE_PATH
target_dir = BASE_PATH / "plugins" / plugin_name
files = [file for file in files if not file.is_dir] files = [file for file in files if not file.is_dir]
download_files = [(file.path, local_path / file.path) for file in files] download_files = [(file.path, local_path / file.path) for file in files]
await RepoFileManager.download_files( await RepoFileManager.download_files(
@ -283,7 +284,7 @@ class StoreManager:
download_files, download_files,
repo_type=repo_type, repo_type=repo_type,
sparse_path=replace_module_path, sparse_path=replace_module_path,
target_dir=local_path / plugin_name, target_dir=target_dir,
) )
requirement_paths = [ requirement_paths = [

View File

@ -11,6 +11,7 @@ from aiocache import cached
from zhenxun.services.log import logger from zhenxun.services.log import logger
from zhenxun.utils.github_utils.models import AliyunFileInfo from zhenxun.utils.github_utils.models import AliyunFileInfo
from zhenxun.utils.repo_utils.utils import prepare_aliyun_url
from .base_manager import BaseRepoManager from .base_manager import BaseRepoManager
from .config import LOG_COMMAND, RepoConfig from .config import LOG_COMMAND, RepoConfig
@ -445,32 +446,6 @@ class AliyunCodeupManager(BaseRepoManager):
返回: 返回:
RepoUpdateResult: 更新结果 RepoUpdateResult: 更新结果
""" """
# 定义预处理函数构建阿里云CodeUp的URL
def prepare_aliyun_url(repo_url: str) -> str:
import base64
repo_name = repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
# 构建仓库URL
# 阿里云CodeUp的仓库URL格式通常为
# https://codeup.aliyun.com/{organization_id}/{organization_name}/{repo_name}.git
url = f"https://codeup.aliyun.com/{self.config.aliyun_codeup.organization_id}/{self.config.aliyun_codeup.organization_name}/{repo_name}.git"
# 添加访问令牌 - 使用base64解码后的令牌
if self.config.aliyun_codeup.rdc_access_token_encrypted:
try:
# 解码RDC访问令牌
token = base64.b64decode(
self.config.aliyun_codeup.rdc_access_token_encrypted.encode()
).decode()
# 阿里云CodeUp使用oauth2:token的格式进行身份验证
url = url.replace("https://", f"https://oauth2:{token}@")
logger.debug(f"使用RDC令牌构建阿里云URL: {url.split('@')[0]}@***")
except Exception as e:
logger.error(f"解码RDC令牌失败: {e}")
return url
# 调用基类的update_via_git方法 # 调用基类的update_via_git方法
return await super().update_via_git( return await super().update_via_git(
repo_url=repo_url, repo_url=repo_url,

View File

@ -66,3 +66,10 @@ class ConfigError(RepoManagerError):
def __init__(self, message: str): def __init__(self, message: str):
super().__init__(f"配置错误: {message}") super().__init__(f"配置错误: {message}")
class GitUnavailableError(RepoManagerError):
"""Git不可用异常"""
def __init__(self, message: str = "Git命令不可用"):
super().__init__(f"Git不可用: {message}")

View File

@ -2,6 +2,7 @@
仓库文件管理器用于从GitHub和阿里云CodeUp获取指定文件内容 仓库文件管理器用于从GitHub和阿里云CodeUp获取指定文件内容
""" """
import contextlib
from pathlib import Path from pathlib import Path
from typing import cast, overload from typing import cast, overload
@ -15,9 +16,14 @@ from zhenxun.utils.http_utils import AsyncHttpx
from zhenxun.utils.utils import is_binary_file from zhenxun.utils.utils import is_binary_file
from .config import LOG_COMMAND, RepoConfig from .config import LOG_COMMAND, RepoConfig
from .exceptions import FileNotFoundError, NetworkError, RepoManagerError from .exceptions import (
FileNotFoundError,
GitUnavailableError,
NetworkError,
RepoManagerError,
)
from .models import FileDownloadResult, RepoFileInfo, RepoType from .models import FileDownloadResult, RepoFileInfo, RepoType
from .utils import sparse_checkout_clone from .utils import prepare_aliyun_url, sparse_checkout_clone
class RepoFileManager: class RepoFileManager:
@ -264,15 +270,15 @@ class RepoFileManager:
if repo_type is None: if repo_type is None:
# 尝试GitHub失败则尝试阿里云 # 尝试GitHub失败则尝试阿里云
try: try:
return await self._list_github_directory_files( return await self._list_aliyun_directory_files(
repo_url, directory_path, branch, recursive repo_name, directory_path, branch, recursive
) )
except Exception as e: except Exception as e:
logger.warning( logger.warning(
"获取GitHub目录文件失败尝试阿里云", LOG_COMMAND, e=e "获取阿里云目录文件失败尝试GitHub", LOG_COMMAND, e=e
) )
return await self._list_aliyun_directory_files( return await self._list_github_directory_files(
repo_name, directory_path, branch, recursive repo_url, directory_path, branch, recursive
) )
if repo_type == RepoType.GITHUB: if repo_type == RepoType.GITHUB:
return await self._list_github_directory_files( return await self._list_github_directory_files(
@ -512,7 +518,7 @@ class RepoFileManager:
) )
if ( if (
any(is_binary_file(file_name) for file_name in file_path_mapping) any(is_binary_file(file_name) for file_name in file_path_mapping)
and repo_type == RepoType.ALIYUN and repo_type != RepoType.GITHUB
and sparse_path and sparse_path
and target_dir and target_dir
): ):
@ -597,7 +603,7 @@ class RepoFileManager:
) -> FileDownloadResult: ) -> FileDownloadResult:
try: try:
await sparse_checkout_clone( await sparse_checkout_clone(
repo_url=repo_url, repo_url=prepare_aliyun_url(repo_url),
branch=branch, branch=branch,
sparse_path=sparse_path, sparse_path=sparse_path,
target_dir=target_dir, target_dir=target_dir,
@ -606,14 +612,17 @@ class RepoFileManager:
if target_dir.exists(): if target_dir.exists():
for f in target_dir.rglob("*"): for f in target_dir.rglob("*"):
if f.is_file(): if f.is_file():
try: with contextlib.suppress(Exception):
total_size += f.stat().st_size total_size += f.stat().st_size
except Exception:
pass
result.success = True result.success = True
result.file_size = total_size result.file_size = total_size
logger.info(f"sparse-checkout 克隆成功: {target_dir}") logger.info(f"sparse-checkout 克隆成功: {target_dir}")
return result return result
except GitUnavailableError as e:
logger.error(f"Git不可用: {e}")
result.success = False
result.error_message = "Git不可用请尝试添加参数 -s git"
return result
except Exception as e: except Exception as e:
logger.error(f"sparse-checkout 克隆失败: {e}") logger.error(f"sparse-checkout 克隆失败: {e}")
result.success = False result.success = False

View File

@ -3,12 +3,15 @@
""" """
import asyncio import asyncio
import base64
from pathlib import Path from pathlib import Path
import re import re
import shutil
from zhenxun.services.log import logger from zhenxun.services.log import logger
from .config import LOG_COMMAND from .config import LOG_COMMAND, RepoConfig
from .exceptions import GitUnavailableError
async def check_git() -> bool: async def check_git() -> bool:
@ -152,7 +155,7 @@ async def sparse_checkout_clone(
target_dir.mkdir(parents=True, exist_ok=True) target_dir.mkdir(parents=True, exist_ok=True)
if not await check_git(): if not await check_git():
raise RuntimeError("未检测到可用的 git 命令") raise GitUnavailableError()
git_dir = target_dir / ".git" git_dir = target_dir / ".git"
if not git_dir.exists(): if not git_dir.exists():
@ -172,15 +175,18 @@ async def sparse_checkout_clone(
# 兜底尝试添加 # 兜底尝试添加
await run_git_command(f"remote add origin {repo_url}", target_dir) await run_git_command(f"remote add origin {repo_url}", target_dir)
# 启用稀疏检出(重复设置以确保幂等 # 启用稀疏检出(使用 --no-cone 模式以获得更精确的控制
await run_git_command("config core.sparseCheckout true", target_dir) await run_git_command("config core.sparseCheckout true", target_dir)
await run_git_command("sparse-checkout init --cone", target_dir) await run_git_command("sparse-checkout init --no-cone", target_dir)
# 设置需要检出的路径(每次都覆盖配置) # 设置需要检出的路径(每次都覆盖配置)
if not sparse_path: if not sparse_path:
raise RuntimeError("sparse-checkout 路径不能为空") raise RuntimeError("sparse-checkout 路径不能为空")
# 使用 --no-cone 模式,直接指定要检出的具体路径
# 例如sparse_path="plugins/mahiro" -> 只检出 plugins/mahiro/ 下的内容
success, out, err = await run_git_command( success, out, err = await run_git_command(
f"sparse-checkout set {sparse_path}", target_dir f"sparse-checkout set {sparse_path}/", target_dir
) )
if not success: if not success:
raise RuntimeError(f"配置稀疏路径失败: {err or out}") raise RuntimeError(f"配置稀疏路径失败: {err or out}")
@ -205,3 +211,44 @@ async def sparse_checkout_clone(
# 强制对齐工作区 # 强制对齐工作区
await run_git_command(f"reset --hard origin/{branch}", target_dir) await run_git_command(f"reset --hard origin/{branch}", target_dir)
await run_git_command("clean -xdf", target_dir) await run_git_command("clean -xdf", target_dir)
dir_path = target_dir / Path(sparse_path)
for f in dir_path.iterdir():
shutil.move(f, target_dir / f.name)
dir_name = sparse_path.split("/")[0]
rm_path = target_dir / dir_name
if rm_path.exists():
shutil.rmtree(rm_path)
def prepare_aliyun_url(repo_url: str) -> str:
"""解析阿里云CodeUp的仓库URL
参数:
repo_url: 仓库URL
返回:
str: 解析后的仓库URL
"""
config = RepoConfig.get_instance()
repo_name = repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
# 构建仓库URL
# 阿里云CodeUp的仓库URL格式通常为
# https://codeup.aliyun.com/{organization_id}/{organization_name}/{repo_name}.git
url = f"https://codeup.aliyun.com/{config.aliyun_codeup.organization_id}/{config.aliyun_codeup.organization_name}/{repo_name}.git"
# 添加访问令牌 - 使用base64解码后的令牌
if config.aliyun_codeup.rdc_access_token_encrypted:
try:
# 解码RDC访问令牌
token = base64.b64decode(
config.aliyun_codeup.rdc_access_token_encrypted.encode()
).decode()
# 阿里云CodeUp使用oauth2:token的格式进行身份验证
url = url.replace("https://", f"https://oauth2:{token}@")
logger.debug(f"使用RDC令牌构建阿里云URL: {url.split('@')[0]}@***")
except Exception as e:
logger.error(f"解码RDC令牌失败: {e}")
return url