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

203 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 使用
```go
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. 打开/创建仓库
```go
// 打开已存在的仓库
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. 基础仓库信息
```go
// 检查仓库是否是空的(无提交)
isEmpty, err := repo.IsEmpty()
// 获取默认分支名
defaultBranch, err := repo.DefaultBranch()
// 切换上下文(例如用于请求超时取消)
newCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
repoWithTimeout := repo.WithContext(newCtx)
```
#### 3. 提交操作
```go
// 获取单个提交(支持 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. 分支和标签
```go
// 列出所有分支
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. 文件和目录操作
```go
// 获取文件内容
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. 统计信息
```go
// 获取仓库整体统计
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()` 方法,它会自动设置工作目录:
```go
// 等同于 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...)` | 文件路径列表 | 加 `--` 前缀,防路径以 `-` 开头注入选项 |
## 典型错误
**错误**:直接拼接用户输入
```go
// 错!如果 ref 是 "--exec=/bin/sh" 怎么办?
gitcmd.NewCommand("show", ref).RunStdString(ctx)
```
**正确**:动态参数走安全方法
```go
// 对!检测以 - 开头的参数并拒绝
gitcmd.NewCommand("show").AddDynamicArguments(ref).RunStdString(ctx)
```
✅ 或者用 `--` 分隔:
```go
gitcmd.NewCommand("show").AddDashesAndList(ref).RunStdString(ctx)
```