From b5417d1141b7570092ea600516576a39cfaf3402 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Thu, 28 Aug 2025 11:05:19 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=BC=BA=E9=98=BF?= =?UTF-8?q?=E9=87=8C=E4=BA=91=E5=92=8CGitHub=E7=9A=84=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?Git=E4=B8=8D=E5=8F=AF=E7=94=A8=E5=BC=82=E5=B8=B8=E5=A4=84?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E4=BC=98=E5=8C=96=E7=A8=80=E7=96=8F=E6=A3=80?= =?UTF-8?q?=E5=87=BA=E9=80=BB=E8=BE=91=EF=BC=8C=E6=8F=90=E5=8D=87=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=8F=AF=E8=AF=BB=E6=80=A7=E5=92=8C=E7=A8=B3=E5=AE=9A?= =?UTF-8?q?=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin_store/data_source.py | 3 +- zhenxun/utils/repo_utils/aliyun_manager.py | 27 +-------- zhenxun/utils/repo_utils/exceptions.py | 7 +++ zhenxun/utils/repo_utils/file_manager.py | 33 +++++++---- zhenxun/utils/repo_utils/utils.py | 57 +++++++++++++++++-- 5 files changed, 83 insertions(+), 44 deletions(-) diff --git a/zhenxun/builtin_plugins/plugin_store/data_source.py b/zhenxun/builtin_plugins/plugin_store/data_source.py index d4cdab7e..6a90f70d 100644 --- a/zhenxun/builtin_plugins/plugin_store/data_source.py +++ b/zhenxun/builtin_plugins/plugin_store/data_source.py @@ -276,6 +276,7 @@ class StoreManager: else: files = [RepoFileInfo(path=f"{replace_module_path}.py", is_dir=False)] 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] download_files = [(file.path, local_path / file.path) for file in files] await RepoFileManager.download_files( @@ -283,7 +284,7 @@ class StoreManager: download_files, repo_type=repo_type, sparse_path=replace_module_path, - target_dir=local_path / plugin_name, + target_dir=target_dir, ) requirement_paths = [ diff --git a/zhenxun/utils/repo_utils/aliyun_manager.py b/zhenxun/utils/repo_utils/aliyun_manager.py index 863a5620..302a24de 100644 --- a/zhenxun/utils/repo_utils/aliyun_manager.py +++ b/zhenxun/utils/repo_utils/aliyun_manager.py @@ -11,6 +11,7 @@ from aiocache import cached from zhenxun.services.log import logger from zhenxun.utils.github_utils.models import AliyunFileInfo +from zhenxun.utils.repo_utils.utils import prepare_aliyun_url from .base_manager import BaseRepoManager from .config import LOG_COMMAND, RepoConfig @@ -445,32 +446,6 @@ class AliyunCodeupManager(BaseRepoManager): 返回: 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方法 return await super().update_via_git( repo_url=repo_url, diff --git a/zhenxun/utils/repo_utils/exceptions.py b/zhenxun/utils/repo_utils/exceptions.py index d508f303..a640dbf3 100644 --- a/zhenxun/utils/repo_utils/exceptions.py +++ b/zhenxun/utils/repo_utils/exceptions.py @@ -66,3 +66,10 @@ class ConfigError(RepoManagerError): def __init__(self, message: str): super().__init__(f"配置错误: {message}") + + +class GitUnavailableError(RepoManagerError): + """Git不可用异常""" + + def __init__(self, message: str = "Git命令不可用"): + super().__init__(f"Git不可用: {message}") diff --git a/zhenxun/utils/repo_utils/file_manager.py b/zhenxun/utils/repo_utils/file_manager.py index cecf7045..e95bdf2a 100644 --- a/zhenxun/utils/repo_utils/file_manager.py +++ b/zhenxun/utils/repo_utils/file_manager.py @@ -2,6 +2,7 @@ 仓库文件管理器,用于从GitHub和阿里云CodeUp获取指定文件内容 """ +import contextlib from pathlib import Path 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 .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 .utils import sparse_checkout_clone +from .utils import prepare_aliyun_url, sparse_checkout_clone class RepoFileManager: @@ -264,15 +270,15 @@ class RepoFileManager: if repo_type is None: # 尝试GitHub,失败则尝试阿里云 try: - return await self._list_github_directory_files( - repo_url, directory_path, branch, recursive + return await self._list_aliyun_directory_files( + repo_name, directory_path, branch, recursive ) except Exception as e: logger.warning( - "获取GitHub目录文件失败,尝试阿里云", LOG_COMMAND, e=e + "获取阿里云目录文件失败,尝试GitHub", LOG_COMMAND, e=e ) - return await self._list_aliyun_directory_files( - repo_name, directory_path, branch, recursive + return await self._list_github_directory_files( + repo_url, directory_path, branch, recursive ) if repo_type == RepoType.GITHUB: return await self._list_github_directory_files( @@ -512,7 +518,7 @@ class RepoFileManager: ) if ( 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 target_dir ): @@ -597,7 +603,7 @@ class RepoFileManager: ) -> FileDownloadResult: try: await sparse_checkout_clone( - repo_url=repo_url, + repo_url=prepare_aliyun_url(repo_url), branch=branch, sparse_path=sparse_path, target_dir=target_dir, @@ -606,14 +612,17 @@ class RepoFileManager: if target_dir.exists(): for f in target_dir.rglob("*"): if f.is_file(): - try: + with contextlib.suppress(Exception): total_size += f.stat().st_size - except Exception: - pass result.success = True result.file_size = total_size logger.info(f"sparse-checkout 克隆成功: {target_dir}") 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: logger.error(f"sparse-checkout 克隆失败: {e}") result.success = False diff --git a/zhenxun/utils/repo_utils/utils.py b/zhenxun/utils/repo_utils/utils.py index 10702c16..5fc03e86 100644 --- a/zhenxun/utils/repo_utils/utils.py +++ b/zhenxun/utils/repo_utils/utils.py @@ -3,12 +3,15 @@ """ import asyncio +import base64 from pathlib import Path import re +import shutil 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: @@ -152,7 +155,7 @@ async def sparse_checkout_clone( target_dir.mkdir(parents=True, exist_ok=True) if not await check_git(): - raise RuntimeError("未检测到可用的 git 命令") + raise GitUnavailableError() git_dir = target_dir / ".git" 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) - # 启用稀疏检出(重复设置以确保幂等) + # 启用稀疏检出(使用 --no-cone 模式以获得更精确的控制) 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: raise RuntimeError("sparse-checkout 路径不能为空") + + # 使用 --no-cone 模式,直接指定要检出的具体路径 + # 例如:sparse_path="plugins/mahiro" -> 只检出 plugins/mahiro/ 下的内容 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: 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("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