Files
simplegit/gitcmd/README.md
T
2026-07-15 11:06:48 -04:00

6.9 KiB
Raw Blame History

gitcmd - 统一 Git 命令执行层

gitcmd 是 simplegit 的底层命令执行器,所有 git shell 调用必须经过这里。

设计原则

  1. 安全优先:禁止用户输入以 - 开头,防止选项注入
  2. 上下文贯穿:所有方法接受 context.Context,支持取消和超时
  3. 环境隔离GIT_CONFIG_NOSYSTEM=1,不受系统全局配置影响
  4. 统一入口:所有 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)