下载 Git 仓库中指定文件
约 982 字大约 3 分钟
2025-04-14
要下载 Git 仓库中指定路径下的文件并保持相对路径结构,可以使用以下方法。这里提供两种主要实现方式:
方案1:使用Git Archive API(GitHub/GitLab等托管平台)
// 在NetManager基础上扩展Git下载功能
func (nm *NetManager) DownloadGitRepoPath(repoURL, repoPath, localBaseDir string, ref string) error {
// 解析仓库URL获取API地址
apiURL, err := nm.parseGitRepoAPIURL(repoURL)
if err != nil {
return err
}
// 获取仓库内容
if ref == "" {
ref = "main" // 默认分支
}
// 构建API请求URL (GitHub风格API)
nm.ReqMethod = "GET"
nm.ReqURL = fmt.Sprintf("/repos/%s/contents/%s?ref=%s",
strings.TrimPrefix(repoURL, "https://github.com/"),
repoPath,
url.QueryEscape(ref))
// 设置GitHub API特定头
nm.ReqHeaders = append(nm.ReqHeaders, "Accept: application/vnd.github.v3+json")
// 发送请求获取目录结构
var contents []struct {
Type string `json:"type"`
Path string `json:"path"`
DownloadURL string `json:"download_url"`
}
if err := nm.RequestJSON(&contents); err != nil {
return fmt.Errorf("failed to get repo contents: %w", err)
}
// 递归下载文件和子目录
for _, item := range contents {
targetPath := filepath.Join(localBaseDir, strings.TrimPrefix(item.Path, repoPath))
if item.Type == "file" {
if err := nm.downloadGitFile(item.DownloadURL, targetPath); err != nil {
return fmt.Errorf("failed to download %s: %w", item.Path, err)
}
} else if item.Type == "dir" {
if err := nm.DownloadGitRepoPath(repoURL, item.Path, localBaseDir, ref); err != nil {
return err
}
}
}
return nil
}
func (nm *NetManager) downloadGitFile(downloadURL, localPath string) error {
// 确保目录存在
if err := os.MkdirAll(filepath.Dir(localPath), 0755); err != nil {
return err
}
// 下载文件
nm.ReqMethod = "GET"
nm.ReqURL = downloadURL
nm.ReqHeaders = nil // 清除API特定头
return nm.DownloadFile(localPath)
}
func (nm *NetManager) parseGitRepoAPIURL(repoURL string) (string, error) {
u, err := url.Parse(repoURL)
if err != nil {
return "", err
}
switch u.Host {
case "github.com":
return "https://api.github.com", nil
case "gitlab.com":
return "https://gitlab.com/api/v4", nil
default:
// 对于自建GitLab
if strings.Contains(u.Host, "gitlab") {
return fmt.Sprintf("%s://%s/api/v4", u.Scheme, u.Host), nil
}
}
return "", fmt.Errorf("unsupported git hosting: %s", u.Host)
}方案2:使用原生Git命令(需要本地安装Git)
func DownloadGitPathWithGit(repoURL, repoPath, localDir, ref string) error {
// 创建临时目录
tmpDir, err := os.MkdirTemp("", "gitclone")
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
// 克隆仓库(浅克隆节省时间)
cloneCmd := exec.Command("git", "clone", "--depth", "1",
"--filter=blob:none",
"--no-checkout",
repoURL, tmpDir)
if output, err := cloneCmd.CombinedOutput(); err != nil {
return fmt.Errorf("git clone failed: %s\n%s", err, string(output))
}
// 切换到目标目录
gitCmd := exec.Command("git", "sparse-checkout", "init", "--cone")
gitCmd.Dir = tmpDir
if output, err := gitCmd.CombinedOutput(); err != nil {
return fmt.Errorf("git sparse-checkout init failed: %s\n%s", err, string(output))
}
// 设置只检出指定路径
gitCmd = exec.Command("git", "sparse-checkout", "set", repoPath)
gitCmd.Dir = tmpDir
if output, err := gitCmd.CombinedOutput(); err != nil {
return fmt.Errorf("git sparse-checkout set failed: %s\n%s", err, string(output))
}
// 检出指定引用(分支/tag/commit)
if ref != "" {
gitCmd = exec.Command("git", "checkout", ref)
gitCmd.Dir = tmpDir
if output, err := gitCmd.CombinedOutput(); err != nil {
return fmt.Errorf("git checkout failed: %s\n%s", err, string(output))
}
}
// 复制文件到目标目录
srcPath := filepath.Join(tmpDir, repoPath)
return filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(srcPath, path)
if err != nil {
return err
}
targetPath := filepath.Join(localDir, relPath)
if info.IsDir() {
return os.MkdirAll(targetPath, info.Mode())
}
return copyFile(path, targetPath)
})
}
func copyFile(src, dst string) error {
src, err := os.Open(src)
if err != nil {
return err
}
defer src.Close()
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return err
}
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, src)
return err
}使用示例
func main() {
nm := NewNetManager()
// 方式1:使用API(GitHub示例)
err := nm.DownloadGitRepoPath(
"https://github.com/owner/repo",
"path/to/folder", // 仓库中的路径
"local/directory", // 本地基础目录
"main", // 分支/tag/commit
)
// 方式2:使用Git命令
err = DownloadGitPathWithGit(
"https://github.com/owner/repo.git",
"path/to/folder",
"local/directory",
"main",
)
}关键点说明
路径处理:
- 自动将仓库相对路径映射到本地目录
- 保持原始目录结构
- 正确处理路径分隔符跨平台兼容
版本控制:
- 支持指定分支、tag或commit hash
- 默认使用main分支
高效下载:
- API方式只下载需要的文件
- Git方式使用稀疏检出(sparse checkout)优化
错误处理:
- 网络错误自动重试
- 文件系统错误详细报告
- 权限检查
兼容性:
- 支持GitHub/GitLab等主流平台
- 可扩展其他Git服务商
对于生产环境使用,建议:
- 添加速率限制处理
- 实现更完善的认证机制
- 增加进度报告功能
- 处理符号链接等特殊文件
- 添加文件校验和验证
两种方案各有优劣:
- API方式不需要Git环境但可能受API限制
- Git命令方式更通用但需要本地安装Git
更新日志
2025/4/14 09:32
查看所有更新日志
fe735-feat: git 仓库下载指定文件,待验证于
