6.9 KiB
6.9 KiB
gitcmd - 统一 Git 命令执行层
gitcmd 是 simplegit 的底层命令执行器,所有 git shell 调用必须经过这里。
设计原则
- 安全优先:禁止用户输入以
-开头,防止选项注入 - 上下文贯穿:所有方法接受
context.Context,支持取消和超时 - 环境隔离:
GIT_CONFIG_NOSYSTEM=1,不受系统全局配置影响 - 统一入口:所有 git 命令走同一条代码路径,便于调试和审计
Repository API 速查表
所有方法都绑定在 Repository 结构体上,开箱即用,自动处理安全校验和路径管理:
| 分类 | 方法 | 功能说明 |
|---|---|---|
| 基础操作 | OpenRepository(ctx, path) |
打开已存在的Git仓库 |
repo.IsEmpty() |
检查仓库是否为空(无任何提交) | |
repo.DefaultBranch() |
获取默认分支名 | |
repo.WithContext(ctx) |
切换上下文(用于超时/取消控制) | |
repo.Command(args...) |
创建绑定当前仓库的自定义Git命令 | |
| 提交操作 | repo.GetCommit(ref) |
获取单个提交的基础信息 |
repo.ListCommits(ref, limit) |
批量列出提交记录,limit<=0表示全量 | |
repo.GetCommitDetail(sha) |
获取提交的完整变更统计(文件列表、行数增减) | |
| 分支/标签 | repo.ListBranches() |
列出所有分支及分支最新提交信息 |
repo.ListTags() |
列出所有标签及标签信息 | |
| 文件/目录 | repo.GetBlob(ref, path) |
获取文件内容及元数据,自动检测二进制文件 |
repo.GetRawContent(ref, path) |
直接获取文件原始内容,无额外元数据,适合下载 | |
repo.GetTree(ref, path) |
获取目录下的文件/子目录列表 | |
| 统计分析 | repo.GetStats() |
获取仓库整体统计(总提交数、分支数、标签数、贡献者等) |
repo.GetContributors(ref) |
获取贡献者列表及贡献统计(提交数、代码行数) | |
| 全局工具 | InitBare(ctx, path) |
初始化新的裸仓库 |
CloneBare(ctx, src, dest) |
克隆远程/本地仓库为裸仓库 |
API 示例
基础 Command 使用
import "simplegit/gitcmd"
// 基础命令(参数是 TRUSTED 的常量)
out, _, err := gitcmd.NewCommand("version").RunStdString(ctx)
// 带选项(仍然是 TRUSTED)
gitcmd.NewCommand("log", "-1", "--pretty=%H").
WithDir(repoPath).
WithTimeout(10*time.Second).
RunStdString(ctx)
// 用户提供的值必须走 AddDynamicArguments
// → 如果 userName 是 "-oCommand=evil",会被直接拒绝
gitcmd.NewCommand("log", "--author").
AddDynamicArguments(userName).
WithDir(repoPath).
RunStdString(ctx)
// 文件列表用 AddDashesAndList(自动加 -- 分隔)
gitcmd.NewCommand("ls-tree", "HEAD").
AddDashesAndList(files...).
WithDir(repoPath).
RunStdString(ctx)
Repository API 使用(推荐,更通用更安全)
Repository 封装了单个 Git 仓库的所有操作,自动处理路径、上下文和安全参数验证,不需要每次手动调用 WithDir。
1. 打开/创建仓库
// 打开已存在的仓库
repo, err := gitcmd.OpenRepository(ctx, "/path/to/repo.git")
if err != nil {
// 处理错误:路径不存在、不是有效Git仓库等
log.Fatal(err)
}
// 初始化新的裸仓库
err := gitcmd.InitBare(ctx, "/path/to/new/repo.git")
// 克隆裸仓库
err := gitcmd.CloneBare(ctx, "https://github.com/user/repo.git", "/path/to/clone.git")
2. 基础仓库信息
// 检查仓库是否是空的(无提交)
isEmpty, err := repo.IsEmpty()
// 获取默认分支名
defaultBranch, err := repo.DefaultBranch()
// 切换上下文(例如用于请求超时取消)
newCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
repoWithTimeout := repo.WithContext(newCtx)
3. 提交操作
// 获取单个提交(支持 SHA、分支名、标签名)
commit, err := repo.GetCommit("main")
fmt.Printf("最新提交: %s %s\n", commit.ShortSHA, commit.Message)
// 列出最近20条提交
commits, err := repo.ListCommits("main", 20)
// 获取提交详情(包含文件变更统计)
detail, err := repo.GetCommitDetail(commit.ID)
fmt.Printf("变更文件数: %d, 新增行: %d, 删除行: %d\n",
detail.Stats.Files, detail.Stats.Additions, detail.Stats.Deletions)
4. 分支和标签
// 列出所有分支
branches, err := repo.ListBranches()
for _, b := range branches {
fmt.Printf("%s %s %s\n", b.Name, b.ShortSHA, b.Message)
}
// 列出所有标签
tags, err := repo.ListTags()
for _, t := range tags {
fmt.Printf("%s %s %s\n", t.Name, t.ShortSHA, t.Message)
}
5. 文件和目录操作
// 获取文件内容
blob, err := repo.GetBlob("main", "README.md")
if !blob.IsBinary {
fmt.Println(blob.Content)
}
// 直接获取原始内容(无元数据,适合下载)
content, err := repo.GetRawContent("main", "path/to/file.go")
// 列出目录内容
tree, err := repo.GetTree("main", "path/to/dir")
for _, entry := range tree.Entries {
fmt.Printf("%s %6d %s\n", entry.Type, entry.Size, entry.Name)
}
6. 统计信息
// 获取仓库整体统计
stats, err := repo.GetStats()
fmt.Printf("总提交数: %d, 分支数: %d, 标签数: %d, 贡献者数: %d\n",
stats.TotalCommits, stats.TotalBranches, stats.TotalTags, len(stats.Contributors))
// 获取贡献者列表(按提交数排序)
contributors, err := repo.GetContributors("main")
for _, c := range contributors {
fmt.Printf("%s <%s>: %d commits, %d lines changed\n",
c.Author, c.Email, c.Commits, c.TotalLines)
}
7. 执行自定义命令
如果需要执行 Repository 没有封装的 Git 命令,可以使用 repo.Command() 方法,它会自动设置工作目录:
// 等同于 git cmd.NewCommand("status").WithDir(repo.Path)
out, _, err := repo.Command("status", "--porcelain").RunStdString(ctx)
// 带用户参数的自定义命令
out, _, err := repo.Command("rev-list", "--count").
AddDynamicArguments(userProvidedRef).
RunStdString(ctx)
安全规则
| 方法 | 用途 | 安全检查 |
|---|---|---|
NewCommand(args...) |
创建命令,子命令和固定选项 | 参数必须不包含 = 或特殊字符 |
Add(args...) |
添加更多选项 | 同上 |
Addf(format, value) |
选项+值组合 | format 必须安全,值不做检查 |
AddDynamicArguments(args...) |
用户输入 | 禁止以 - 开头 |
AddDashesAndList(list...) |
文件路径列表 | 加 -- 前缀,防路径以 - 开头注入选项 |
典型错误
❌ 错误:直接拼接用户输入
// 错!如果 ref 是 "--exec=/bin/sh" 怎么办?
gitcmd.NewCommand("show", ref).RunStdString(ctx)
✅ 正确:动态参数走安全方法
// 对!检测以 - 开头的参数并拒绝
gitcmd.NewCommand("show").AddDynamicArguments(ref).RunStdString(ctx)
✅ 或者用 -- 分隔:
gitcmd.NewCommand("show").AddDashesAndList(ref).RunStdString(ctx)