# Git 集成 · 一个 agent 为什么用了三套 git

> git 在编码 agent 里无处不在。grok 同时用了三套：git CLI（给模型看的状态）、gix（纯 Rust 的脏文件扫描）、git2（图谱的文件收集）。这一章看它们各自的活，重点讲一个精彩的坑：在 panic=abort + 紧 RLIMIT_NPROC 下，gix 并行扫描怎么把整个进程搞崩、又怎么救回来。

- 作者：David（道雾轩）
- 专栏：Grok Build 源码解读（https://daiw.org/manual/grok-build.md）
- 最后更新：2026-09-15
- 原文：https://daiw.org/manual/grok-build/git-integration
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# Git 集成 · 一个 agent 为什么用了三套 git

编码 agent 里 git 无处不在：看状态、算 diff、提交、切分支、开 worktree。有意思的是，grok **同时用了三套 git 实现**，各干各的活。

## 三套 git，三种用途

| 用途 | 用什么 | 在哪 |
| --- | --- | --- |
| 给**模型看**的状态(prompt 格式) | **git CLI** | `file_system/git_status.rs` |
| 脏/暂存文件**扫描** | **gix**(纯 Rust) | [hunk-tracker](https://daiw.org/manual/grok-build/hunk-tracker)、[worktree](https://daiw.org/manual/grok-build/fast-worktree) |
| [代码库图谱](https://daiw.org/manual/grok-build/codebase-graph-build)的**文件收集** | **git2**(libgit2) | 图谱构建器 |

为什么不统一成一套？因为各有所长：CLI 输出的格式模型最熟；gix 纯 Rust、无 FFI、适合高频扫描；git2 成熟、拿 tracked 文件列表快。**「按场景选实现，而非教条统一」**本身就是一种工程成熟度。

## 一个精彩的坑：gix 并行扫描会崩掉整个进程

`xai-gix-status` 这个小 crate 存在的理由，是一个非常真实、非常隐蔽的坑，值得单独讲。

gix 的并行状态扫描(`in_parallel`)内部是 `spawn_scoped(...).expect("valid name")`。问题来了：

- grok 用 **`panic = "abort"`** 编译(panic 直接 abort 进程，不 unwind);
- 若系统 **`RLIMIT_NPROC`**(进程/线程数上限)很紧(容器里常见)，线程 spawn 会失败；
- 失败的 `spawn_scoped` 里那个 `.expect()` 就 panic → 在 `panic=abort` 下**直接 abort 整个进程**。

也就是说：**在受限容器里跑一次 `git status`，可能让整个 agent 进程猝死**。而且这不是能 `catch` 的 `JoinError`，是 SIGABRT。

`xai-gix-status` 的解法是**给并行扫描的线程数上限做预算**：`compute_gix_status_thread_limit()` 综合 CPU 核数(封顶 `HARD_CAP=8`)、`RLIMIT_NPROC` 软上限、当前已用线程数(`/proc/self/status`)，留出 `OUTER_RESERVE=8` 给非 gix 线程，算出一个**保证 spawn 不会失败**的线程数。所有 gix 状态调用都用 `with_budgeted_thread_limit(...)` 包一层。

<Callout type="warn">
  一个致命细节：gix 里 `thread_limit = Some(0)` 表示**无限制**，不是「零线程」。所以这个 crate 里有 `debug_assert!(!Some(0))` 守卫，永远不会传 0。这种「0 == 无限」的 API 反直觉设定，正是并发 bug 的温床——读源码时要格外警惕。
</Callout>

它的测试也很讲究：在**子进程**里调低 `RLIMIT_NPROC`(父进程绝不碰 rlimit，否则并行 `cargo test` 会互相干扰)，证明「无预算的扫描确实 abort、有预算的活下来」。

## 还有个空索引的坑

`gix-index` 在 mmap 一个 0 字节的 `.git/index` 时会 panic(切尾部哈希越界)。所以 hunk-tracker 扫描前先查 `index_path.metadata().len() == 0`。**又一个「真实仓库里真会遇到、玩具里永远碰不到」的边界**。

## 以及 libgit2 的并发坑

git2 也不只图谱在用：workspace 里会话的 git 视图(`session/git.rs`)同样在进程内调 libgit2 做 status/diff。新代码为此加了一道进程级闸门(`session/git_gate.rs`)：相同的在途请求直接合并、短时间内复用快照；再配一个 ODB 许可(`git_odb.rs`)。原因写在注释里——并发的 libgit2 遍历会反复读同一批 pack，在浅克隆的 monorepo 上能把 CPU 打满、占掉几十 GB 内存。

## 和《从 LLM 到 Coding Agent》对照

那本里 git 集成大概就是 `exec('git status')`。grok 的增量在于：三套实现各司其职，以及处理那些**只有在真实、受限环境里才暴露**的坑——`panic=abort` × `RLIMIT_NPROC` 的进程猝死、空索引 panic、libgit2 并发遍历把内存吃爆。这些坑不改变功能，却决定产品在容器/CI 里稳不稳。下一部分继续：它怎么给你的代码建一张符号图。

---

上一篇：[文件监听 · fsnotify](https://daiw.org/manual/grok-build/fsnotify) · 下一篇：[代码库图谱 · 构建](https://daiw.org/manual/grok-build/codebase-graph-build)
