mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-15 14:22:55 +08:00
558 lines
18 KiB
Python
558 lines
18 KiB
Python
|
|
"""
|
|||
|
|
阿里云CodeUp仓库管理工具
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import asyncio
|
|||
|
|
from collections.abc import Callable
|
|||
|
|
from datetime import datetime
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
from aiocache import cached
|
|||
|
|
|
|||
|
|
from zhenxun.services.log import logger
|
|||
|
|
from zhenxun.utils.github_utils.models import AliyunFileInfo
|
|||
|
|
|
|||
|
|
from .base_manager import BaseRepoManager
|
|||
|
|
from .config import LOG_COMMAND, RepoConfig
|
|||
|
|
from .exceptions import (
|
|||
|
|
AuthenticationError,
|
|||
|
|
FileNotFoundError,
|
|||
|
|
RepoDownloadError,
|
|||
|
|
RepoNotFoundError,
|
|||
|
|
RepoUpdateError,
|
|||
|
|
)
|
|||
|
|
from .models import (
|
|||
|
|
FileDownloadResult,
|
|||
|
|
RepoCommitInfo,
|
|||
|
|
RepoFileInfo,
|
|||
|
|
RepoType,
|
|||
|
|
RepoUpdateResult,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AliyunCodeupManager(BaseRepoManager):
|
|||
|
|
"""阿里云CodeUp仓库管理工具"""
|
|||
|
|
|
|||
|
|
def __init__(self, config: RepoConfig | None = None):
|
|||
|
|
"""
|
|||
|
|
初始化阿里云CodeUp仓库管理工具
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
config: 配置,如果为None则使用默认配置
|
|||
|
|
"""
|
|||
|
|
super().__init__(config)
|
|||
|
|
self._client = None
|
|||
|
|
|
|||
|
|
async def update_repo(
|
|||
|
|
self,
|
|||
|
|
repo_url: str,
|
|||
|
|
local_path: Path,
|
|||
|
|
branch: str = "main",
|
|||
|
|
include_patterns: list[str] | None = None,
|
|||
|
|
exclude_patterns: list[str] | None = None,
|
|||
|
|
) -> RepoUpdateResult:
|
|||
|
|
"""
|
|||
|
|
更新阿里云CodeUp仓库
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
repo_url: 仓库URL或名称
|
|||
|
|
local_path: 本地保存路径
|
|||
|
|
branch: 分支名称
|
|||
|
|
include_patterns: 包含的文件模式列表,如 ["*.py", "docs/*.md"]
|
|||
|
|
exclude_patterns: 排除的文件模式列表,如 ["__pycache__/*", "*.pyc"]
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
RepoUpdateResult: 更新结果
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
# 检查配置
|
|||
|
|
self._check_config()
|
|||
|
|
|
|||
|
|
# 获取仓库名称(从URL中提取)
|
|||
|
|
repo_url = repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
|
|||
|
|
|
|||
|
|
# 获取仓库最新提交ID
|
|||
|
|
newest_commit = await self._get_newest_commit(repo_url, branch)
|
|||
|
|
|
|||
|
|
# 创建结果对象
|
|||
|
|
result = RepoUpdateResult(
|
|||
|
|
repo_type=RepoType.ALIYUN,
|
|||
|
|
repo_name=repo_url.split("/tree/")[0]
|
|||
|
|
.split("/")[-1]
|
|||
|
|
.replace(".git", ""),
|
|||
|
|
owner=self.config.aliyun_codeup.organization_id,
|
|||
|
|
old_version="", # 将在后面更新
|
|||
|
|
new_version=newest_commit,
|
|||
|
|
)
|
|||
|
|
old_version = await self.read_version_file(local_path)
|
|||
|
|
result.old_version = old_version
|
|||
|
|
|
|||
|
|
# 如果版本相同,则无需更新
|
|||
|
|
if old_version == newest_commit:
|
|||
|
|
result.success = True
|
|||
|
|
logger.debug(
|
|||
|
|
f"仓库 {repo_url.split('/')[-1].replace('.git', '')}"
|
|||
|
|
f" 已是最新版本: {newest_commit[:8]}",
|
|||
|
|
LOG_COMMAND,
|
|||
|
|
)
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
# 确保本地目录存在
|
|||
|
|
local_path.mkdir(parents=True, exist_ok=True)
|
|||
|
|
|
|||
|
|
# 获取仓库名称(从URL中提取)
|
|||
|
|
repo_name = repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
|
|||
|
|
|
|||
|
|
# 获取变更的文件列表
|
|||
|
|
changed_files = await self._get_changed_files(
|
|||
|
|
repo_name, old_version or None, newest_commit
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 过滤文件
|
|||
|
|
if include_patterns or exclude_patterns:
|
|||
|
|
from .utils import filter_files
|
|||
|
|
|
|||
|
|
changed_files = filter_files(
|
|||
|
|
changed_files, include_patterns, exclude_patterns
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
result.changed_files = changed_files
|
|||
|
|
|
|||
|
|
# 下载变更的文件
|
|||
|
|
for file_path in changed_files:
|
|||
|
|
try:
|
|||
|
|
local_file_path = local_path / file_path
|
|||
|
|
await self._download_file(
|
|||
|
|
repo_name, file_path, local_file_path, newest_commit
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"下载文件 {file_path} 失败", LOG_COMMAND, e=e)
|
|||
|
|
|
|||
|
|
# 更新版本文件
|
|||
|
|
await self.write_version_file(local_path, newest_commit)
|
|||
|
|
|
|||
|
|
result.success = True
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
except RepoUpdateError as e:
|
|||
|
|
logger.error(f"更新仓库失败: {e}")
|
|||
|
|
# 从URL中提取仓库名称
|
|||
|
|
repo_name = repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
|
|||
|
|
return RepoUpdateResult(
|
|||
|
|
repo_type=RepoType.ALIYUN,
|
|||
|
|
repo_name=repo_name,
|
|||
|
|
owner=self.config.aliyun_codeup.organization_id,
|
|||
|
|
old_version="",
|
|||
|
|
new_version="",
|
|||
|
|
error_message=str(e),
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"更新仓库失败: {e}")
|
|||
|
|
# 从URL中提取仓库名称
|
|||
|
|
repo_name = repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
|
|||
|
|
return RepoUpdateResult(
|
|||
|
|
repo_type=RepoType.ALIYUN,
|
|||
|
|
repo_name=repo_name,
|
|||
|
|
owner=self.config.aliyun_codeup.organization_id,
|
|||
|
|
old_version="",
|
|||
|
|
new_version="",
|
|||
|
|
error_message=str(e),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def download_file(
|
|||
|
|
self,
|
|||
|
|
repo_url: str,
|
|||
|
|
file_path: str,
|
|||
|
|
local_path: Path,
|
|||
|
|
branch: str = "main",
|
|||
|
|
) -> FileDownloadResult:
|
|||
|
|
"""
|
|||
|
|
从阿里云CodeUp下载单个文件
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
repo_url: 仓库URL或名称
|
|||
|
|
file_path: 文件在仓库中的路径
|
|||
|
|
local_path: 本地保存路径
|
|||
|
|
branch: 分支名称
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
FileDownloadResult: 下载结果
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
# 检查配置
|
|||
|
|
self._check_config()
|
|||
|
|
|
|||
|
|
# 获取仓库名称(从URL中提取)
|
|||
|
|
repo_identifier = (
|
|||
|
|
repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 创建结果对象
|
|||
|
|
result = FileDownloadResult(
|
|||
|
|
repo_type=RepoType.ALIYUN,
|
|||
|
|
repo_name=repo_url.split("/tree/")[0]
|
|||
|
|
.split("/")[-1]
|
|||
|
|
.replace(".git", ""),
|
|||
|
|
file_path=file_path,
|
|||
|
|
version=branch,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 确保本地目录存在
|
|||
|
|
Path(local_path).parent.mkdir(parents=True, exist_ok=True)
|
|||
|
|
|
|||
|
|
# 下载文件
|
|||
|
|
file_size = await self._download_file(
|
|||
|
|
repo_identifier, file_path, local_path, branch
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
result.success = True
|
|||
|
|
result.file_size = file_size
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
except RepoDownloadError as e:
|
|||
|
|
logger.error(f"下载文件失败: {e}")
|
|||
|
|
# 从URL中提取仓库名称
|
|||
|
|
repo_name = repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
|
|||
|
|
return FileDownloadResult(
|
|||
|
|
repo_type=RepoType.ALIYUN,
|
|||
|
|
repo_name=repo_name,
|
|||
|
|
file_path=file_path,
|
|||
|
|
version=branch,
|
|||
|
|
error_message=str(e),
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"下载文件失败: {e}")
|
|||
|
|
# 从URL中提取仓库名称
|
|||
|
|
repo_name = repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
|
|||
|
|
return FileDownloadResult(
|
|||
|
|
repo_type=RepoType.ALIYUN,
|
|||
|
|
repo_name=repo_name,
|
|||
|
|
file_path=file_path,
|
|||
|
|
version=branch,
|
|||
|
|
error_message=str(e),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def get_file_list(
|
|||
|
|
self,
|
|||
|
|
repo_url: str,
|
|||
|
|
dir_path: str = "",
|
|||
|
|
branch: str = "main",
|
|||
|
|
recursive: bool = False,
|
|||
|
|
) -> list[RepoFileInfo]:
|
|||
|
|
"""
|
|||
|
|
获取仓库文件列表
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
repo_url: 仓库URL或名称
|
|||
|
|
dir_path: 目录路径,空字符串表示仓库根目录
|
|||
|
|
branch: 分支名称
|
|||
|
|
recursive: 是否递归获取子目录
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
list[RepoFileInfo]: 文件信息列表
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
# 检查配置
|
|||
|
|
self._check_config()
|
|||
|
|
|
|||
|
|
# 获取仓库名称(从URL中提取)
|
|||
|
|
repo_identifier = (
|
|||
|
|
repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 获取文件列表
|
|||
|
|
search_type = "RECURSIVE" if recursive else "DIRECT"
|
|||
|
|
tree_list = await AliyunFileInfo.get_repository_tree(
|
|||
|
|
repo_identifier, dir_path, branch, search_type
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
result = []
|
|||
|
|
for tree in tree_list:
|
|||
|
|
# 跳过非当前目录的文件(如果不是递归模式)
|
|||
|
|
if (
|
|||
|
|
not recursive
|
|||
|
|
and tree.path != dir_path
|
|||
|
|
and "/" in tree.path.replace(dir_path, "", 1).strip("/")
|
|||
|
|
):
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
file_info = RepoFileInfo(
|
|||
|
|
path=tree.path,
|
|||
|
|
is_dir=tree.type == "tree",
|
|||
|
|
)
|
|||
|
|
result.append(file_info)
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"获取文件列表失败: {e}")
|
|||
|
|
return []
|
|||
|
|
|
|||
|
|
async def get_commit_info(
|
|||
|
|
self, repo_url: str, commit_id: str
|
|||
|
|
) -> RepoCommitInfo | None:
|
|||
|
|
"""
|
|||
|
|
获取提交信息
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
repo_url: 仓库URL或名称
|
|||
|
|
commit_id: 提交ID
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Optional[RepoCommitInfo]: 提交信息,如果获取失败则返回None
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
# 检查配置
|
|||
|
|
self._check_config()
|
|||
|
|
|
|||
|
|
# 获取仓库名称(从URL中提取)
|
|||
|
|
repo_identifier = (
|
|||
|
|
repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 获取提交信息
|
|||
|
|
# 注意:这里假设AliyunFileInfo有get_commit_info方法,如果没有,需要实现
|
|||
|
|
commit_data = await self._get_commit_info(repo_identifier, commit_id)
|
|||
|
|
|
|||
|
|
if not commit_data:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
# 解析提交信息
|
|||
|
|
id_value = commit_data.get("id", commit_id)
|
|||
|
|
message_value = commit_data.get("message", "")
|
|||
|
|
author_value = commit_data.get("author_name", "")
|
|||
|
|
date_value = commit_data.get(
|
|||
|
|
"authored_date", datetime.now().isoformat()
|
|||
|
|
).replace("Z", "+00:00")
|
|||
|
|
|
|||
|
|
return RepoCommitInfo(
|
|||
|
|
commit_id=id_value,
|
|||
|
|
message=message_value,
|
|||
|
|
author=author_value,
|
|||
|
|
commit_time=datetime.fromisoformat(date_value),
|
|||
|
|
changed_files=[], # 阿里云API可能没有直接提供变更文件列表
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"获取提交信息失败: {e}")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def _check_config(self):
|
|||
|
|
"""检查配置"""
|
|||
|
|
if not self.config.aliyun_codeup.access_key_id:
|
|||
|
|
raise AuthenticationError("阿里云CodeUp")
|
|||
|
|
|
|||
|
|
if not self.config.aliyun_codeup.access_key_secret:
|
|||
|
|
raise AuthenticationError("阿里云CodeUp")
|
|||
|
|
|
|||
|
|
if not self.config.aliyun_codeup.organization_id:
|
|||
|
|
raise AuthenticationError("阿里云CodeUp")
|
|||
|
|
|
|||
|
|
async def _get_newest_commit(self, repo_name: str, branch: str) -> str:
|
|||
|
|
"""
|
|||
|
|
获取仓库最新提交ID
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
repo_name: 仓库名称
|
|||
|
|
branch: 分支名称
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
str: 提交ID
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
newest_commit = await AliyunFileInfo.get_newest_commit(repo_name, branch)
|
|||
|
|
if not newest_commit:
|
|||
|
|
raise RepoNotFoundError(repo_name)
|
|||
|
|
return newest_commit
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"获取最新提交ID失败: {e}")
|
|||
|
|
raise RepoUpdateError(f"获取最新提交ID失败: {e}")
|
|||
|
|
|
|||
|
|
async def _get_commit_info(self, repo_name: str, commit_id: str) -> dict:
|
|||
|
|
"""
|
|||
|
|
获取提交信息
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
repo_name: 仓库名称
|
|||
|
|
commit_id: 提交ID
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
dict: 提交信息
|
|||
|
|
"""
|
|||
|
|
# 这里需要实现从阿里云获取提交信息的逻辑
|
|||
|
|
# 由于AliyunFileInfo可能没有get_commit_info方法,这里提供一个简单的实现
|
|||
|
|
try:
|
|||
|
|
# 这里应该是调用阿里云API获取提交信息
|
|||
|
|
# 这里只是一个示例,实际上需要根据阿里云API实现
|
|||
|
|
return {
|
|||
|
|
"id": commit_id,
|
|||
|
|
"message": "提交信息",
|
|||
|
|
"author_name": "作者",
|
|||
|
|
"authored_date": datetime.now().isoformat(),
|
|||
|
|
}
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"获取提交信息失败: {e}")
|
|||
|
|
return {}
|
|||
|
|
|
|||
|
|
@cached(ttl=3600)
|
|||
|
|
async def _get_changed_files(
|
|||
|
|
self, repo_name: str, old_commit: str | None, new_commit: str
|
|||
|
|
) -> list[str]:
|
|||
|
|
"""
|
|||
|
|
获取两个提交之间变更的文件列表
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
repo_name: 仓库名称
|
|||
|
|
old_commit: 旧提交ID,如果为None则获取所有文件
|
|||
|
|
new_commit: 新提交ID
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
list[str]: 变更的文件列表
|
|||
|
|
"""
|
|||
|
|
if not old_commit:
|
|||
|
|
# 如果没有旧提交,则获取仓库中的所有文件
|
|||
|
|
tree_list = await AliyunFileInfo.get_repository_tree(
|
|||
|
|
repo_name, "", new_commit, "RECURSIVE"
|
|||
|
|
)
|
|||
|
|
return [tree.path for tree in tree_list if tree.type == "blob"]
|
|||
|
|
|
|||
|
|
# 获取两个提交之间的差异
|
|||
|
|
try:
|
|||
|
|
return []
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"获取提交差异失败: {e}")
|
|||
|
|
raise RepoUpdateError(f"获取提交差异失败: {e}")
|
|||
|
|
|
|||
|
|
async def update_via_git(
|
|||
|
|
self,
|
|||
|
|
repo_url: str,
|
|||
|
|
local_path: Path,
|
|||
|
|
branch: str = "main",
|
|||
|
|
force: bool = False,
|
|||
|
|
*,
|
|||
|
|
repo_type: RepoType | None = None,
|
|||
|
|
owner: str | None = None,
|
|||
|
|
prepare_repo_url: Callable[[str], str] | None = None,
|
|||
|
|
) -> RepoUpdateResult:
|
|||
|
|
"""
|
|||
|
|
通过Git命令直接更新仓库
|
|||
|
|
|
|||
|
|
参数:
|
|||
|
|
repo_url: 仓库名称
|
|||
|
|
local_path: 本地仓库路径
|
|||
|
|
branch: 分支名称
|
|||
|
|
force: 是否强制拉取
|
|||
|
|
|
|||
|
|
返回:
|
|||
|
|
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,
|
|||
|
|
local_path=local_path,
|
|||
|
|
branch=branch,
|
|||
|
|
force=force,
|
|||
|
|
repo_type=RepoType.ALIYUN,
|
|||
|
|
owner=self.config.aliyun_codeup.organization_id,
|
|||
|
|
prepare_repo_url=prepare_aliyun_url,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def update(
|
|||
|
|
self,
|
|||
|
|
repo_url: str,
|
|||
|
|
local_path: Path,
|
|||
|
|
branch: str = "main",
|
|||
|
|
use_git: bool = True,
|
|||
|
|
force: bool = False,
|
|||
|
|
include_patterns: list[str] | None = None,
|
|||
|
|
exclude_patterns: list[str] | None = None,
|
|||
|
|
) -> RepoUpdateResult:
|
|||
|
|
"""
|
|||
|
|
更新仓库,可选择使用Git命令或API方式
|
|||
|
|
|
|||
|
|
参数:
|
|||
|
|
repo_url: 仓库名称
|
|||
|
|
local_path: 本地保存路径
|
|||
|
|
branch: 分支名称
|
|||
|
|
use_git: 是否使用Git命令更新
|
|||
|
|
include_patterns: 包含的文件模式列表,如 ["*.py", "docs/*.md"]
|
|||
|
|
exclude_patterns: 排除的文件模式列表,如 ["__pycache__/*", "*.pyc"]
|
|||
|
|
|
|||
|
|
返回:
|
|||
|
|
RepoUpdateResult: 更新结果
|
|||
|
|
"""
|
|||
|
|
if use_git:
|
|||
|
|
return await self.update_via_git(repo_url, local_path, branch, force)
|
|||
|
|
else:
|
|||
|
|
return await self.update_repo(
|
|||
|
|
repo_url, local_path, branch, include_patterns, exclude_patterns
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def _download_file(
|
|||
|
|
self, repo_name: str, file_path: str, local_path: Path, ref: str
|
|||
|
|
) -> int:
|
|||
|
|
"""
|
|||
|
|
下载文件
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
repo_name: 仓库名称
|
|||
|
|
file_path: 文件在仓库中的路径
|
|||
|
|
local_path: 本地保存路径
|
|||
|
|
ref: 分支/标签/提交ID
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
int: 文件大小(字节)
|
|||
|
|
"""
|
|||
|
|
# 确保目录存在
|
|||
|
|
local_path.parent.mkdir(parents=True, exist_ok=True)
|
|||
|
|
|
|||
|
|
# 获取文件内容
|
|||
|
|
for retry in range(self.config.aliyun_codeup.download_retry + 1):
|
|||
|
|
try:
|
|||
|
|
content = await AliyunFileInfo.get_file_content(
|
|||
|
|
file_path, repo_name, ref
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if content is None:
|
|||
|
|
raise FileNotFoundError(file_path, repo_name)
|
|||
|
|
|
|||
|
|
# 保存文件
|
|||
|
|
return await self.save_file_content(content.encode("utf-8"), local_path)
|
|||
|
|
|
|||
|
|
except FileNotFoundError as e:
|
|||
|
|
# 这些错误不需要重试
|
|||
|
|
raise e
|
|||
|
|
except Exception as e:
|
|||
|
|
if retry < self.config.aliyun_codeup.download_retry:
|
|||
|
|
logger.warning("下载文件失败,将重试", LOG_COMMAND, e=e)
|
|||
|
|
await asyncio.sleep(1)
|
|||
|
|
continue
|
|||
|
|
raise RepoDownloadError(f"下载文件失败: {e}")
|
|||
|
|
|
|||
|
|
raise RepoDownloadError("下载文件失败: 超过最大重试次数")
|