跳转到内容

gitui — Rust 写的 git TUI,libgit2 直连让启动比 lazygit 快一个量级

是什么

gitui 是 Stephan Dilly(GitHub: extrawurst)用 Rust 写的 git 终端图形界面,把 status / stage / log / branches / stash 这些 git 日常动作放进一个多面板单屏 TUI。它和 lazygit 长得像、操作也类似,但底层走的是完全不同的路线:lazygit 每次都 fork 一个 git 子进程跑,gitui 直接用 libgit2 的 Rust 绑定读写仓库——两条路线的工程代价和性能差异就是这篇笔记的主题。

日常类比:

  • lazygit 是装了菜单的快递员——每次你点一下,他还是要跑去 git 那家店替你下单
  • gitui 是把店搬进自己家——所有 git 数据结构(object database / index / refs)直接在内存里读,不出门

数据上看:解析 Linux 内核 90 万 commit 的 log,gitui 用 24 秒、占 0.17GB 内存、10MB 二进制;同样的事 lazygit 要 57 秒、2.6GB 内存、25MB 二进制。截至 2026-05,v0.28.1,22k stars。

为什么重要

不理解 gitui 和 lazygit 的分歧,下面这些事都没法解释:

  • 为什么 TUI 工具的真正瓶颈是子进程开销,不是终端渲染——这个洞见可以泛化到很多 CLI 包装层
  • 为什么 gitui 故意不支持 git-LFS / sparse-checkout——这不是没做完,是 libgit2 路线的结构性代价
  • 为什么”Rust + 直接绑定 C 库”这条工程模式正在大量复制(ripgrep 直接读文件不调 grep,delta 直接做 diff 渲染不调 less)
  • 为什么同一个品类可以有两个明星项目并存——它们其实在解不同的问题

核心要点

gitui 的设计可以拆成 4 件事

  1. libgit2-rs 直连仓库:libgit2 是把 git 内部结构(commit 对象、tree、blob、refs、index)写成 C 库的实现,gitui 通过 Rust 绑定 git2-rs 直接调它的函数,不 fork 任何子进程。读 status 就是遍历内存里的 index,看 log 就是顺着 commit 父指针走 DAG——这是它快一个量级的根本原因。

  2. 异步 git 操作 + 单线程 UI:耗时操作(fetch / 大仓 commit graph 构建 / blame)扔进后台线程,UI 线程只画屏幕、收键盘。哪怕仓库在拉远端,你按方向键还是立即响应。lazygit 也做了类似异步,但子进程本身就是开销。

  3. 面板 + 单键键位:默认面板 Status / Files / Log / Stashes / Branches,Tab 切换,每个面板内 vim 风格 hjkl 移光标,单字母触发动作(s stage、u unstage、c commit、p push)。设计哲学和 lazygit / btop 同源——多面板上下文 + 单键操作减少手指移动。

  4. MIT 协议 + Rust 单二进制cargo install gitui 一行装好,10MB 静态可执行,无运行时依赖。这种”装一个就能跑”的体验和 ripgrep / fzf / bat 共享同一个 Rust CLI 时代的红利。

实践案例

案例 1:在 90 万 commit 的大仓库里看 log

Terminal window
# 先克隆 Linux 内核(约 6GB,含 .git)
git clone https://github.com/torvalds/linux
cd linux
# 用 lazygit 打开 → Log 面板加载 ~57s,进度条卡住
lazygit
# 用 gitui 打开 → Log 面板加载 ~24s,过程中可继续切其他面板
gitui

差别不是渲染——两边都是终端文字——而是 lazygit 每滚一屏 commit 都要再 fork 一次 git log,gitui 是已经把 commit graph 加载到内存里直接遍历。

案例 2:行级 stage(和 lazygit 一样但更快)

Terminal window
gitui
# 进 Files 面板(按 2),光标移到一个改动的文件
# 回车进入文件 diff 视图
# 在某行按 s → 只 stage 这一行;按 hunk 按 S → stage 整个 hunk
# 退回后看 Status,只 stage 的那一行已经在 staged 区

操作语义和 lazygit / deltagit add -p 一致,但 gitui 的 stage 动作直接改 index 文件、不调 git add

案例 3:什么时候 gitui 帮不了你

Terminal window
# 仓库里有 LFS 大文件
git lfs install
gitui
# → checkout 时 LFS pointer 不会自动 smudge,文件依然是 140 字节的 metadata
# 仓库是 sparse-checkout 模式
git sparse-checkout init --cone
gitui
# → 看不到 sparse 配置,可能错把不在 sparse 范围的文件展示出来

这两个是 libgit2 跟不上 git 主仓的典型病症——遇到这类仓库要 fallback 回 lazygit 或 git CLI。

案例 4:tmux + gitui 的常见工作流

Terminal window
# tmux 三窗格:左编辑器、右上 gitui、右下 shell
tmux new-session \; split-window -h \; split-window -v
# 在右上窗格跑 gitui,因为它启动 < 100ms,所以可以 Ctrl-c 关掉再开都不卡

这种”频繁开关”工作流是 gitui 比 lazygit 体验拉开最大的场景——lazygit 启动要等几百毫秒,gitui 几乎瞬间就回来。

踩过的坑

  1. HTTPS 推拉要显式配 credential.helper:lazygit 直接复用你 shell 里的 git 配置;gitui 通过 libgit2,不会自动读 osxkeychain / wincred,第一次 push 容易卡死要手动 git config credential.helper。具体的修法是在仓库根跑 git config credential.helper osxkeychain(macOS)或 manager(Windows),改完 gitui 才能复用 keychain 里的 token。

  2. 大仓首次打开有 cold start:24 秒的 90 万 commit 数字是热路径,首次构建 commit graph 内部缓存要更久。在 Linux 内核这种仓库上等 30 秒+ 是正常的。后续打开因为缓存命中快很多。

  3. 键位和 lazygit 不通用:都是单字母 vim 风,但具体绑定不一致——比如 lazygit 的 s 是 squash,gitui 的 s 是 stage。两个都用要分别记,建议把不常用那个的快捷键打印出来贴显示器。

  4. 不能完全替代 git CLI:作者在 README 明确写”this is not a full replacement for the git shell”——rebase 冲突解决、复杂 reflog 恢复、submodule 一些边角操作还得回到 CLI。当 daily 主力工具,不当唯一工具。

  5. theme 配置藏得深:默认主题对暗色终端不一定友好,要在 ~/.config/gitui/theme.ron 用 RON 格式(不是 YAML / TOML)写颜色,文档里只有几行示例。新人改主题前最好直接 fork 一份社区的 dark / light theme 当模板。

适用 vs 不适用场景

适用

  • 大仓库 daily 操作(看 log / stage / commit / branch 切换)——速度优势最明显
  • 对启动延迟敏感的工作流(频繁开关 git 客户端、tmux 里来回切窗口)
  • 喜欢 vim 键位 + Rust 单二进制无依赖的洁癖
  • 已经在用 ripgrep / bat / fzf / delta 这套 Rust CLI 生态——风格统一
  • CI / Docker 镜像里要塞个 git TUI 但又想控制镜像大小(10MB vs 25MB 的差距在容器里也算钱)

不适用

  • 仓库重度用 LFS / sparse-checkout / partial clone——libgit2 跟不上
  • 团队有大量交互式 rebase 冲突场景——gitui 的冲突解决体验不如 lazygit 成熟
  • 完全不习惯键盘驱动 TUI、想要鼠标点击——这两个工具都不适合,去看 GitKraken / Sourcetree
  • 对 GPG / SSH 签名链路敏感——gitui 支持 GPG 但和你 shell 配置可能行为不一致
  • 需要插件生态(自定义命令、第三方扩展)——gitui 还没开放插件接口,lazygit 的 customCommands 灵活得多

历史小故事(可跳过)

  • 2019 年:Stephan Dilly 因为大仓库下 lazygit 启动慢起念头自己写一个 Rust 版。当时 Rust 的 TUI 库还很原始,他先用 tui-rs(后来改名 ratatui)打磨了几个月底层
  • 2020 年初:v0.1 发布,定位明确”就是要比 lazygit 快”。第一周就上了 HN 头版,star 数过千
  • 2021–2024:随 Rust TUI 生态(ratatui)成熟,UI 体验持续打磨。期间最大的两次架构调整是异步任务系统重写、commit graph 内部缓存格式升级
  • 2026 年:v0.28,22k stars,已经是 Rust CLI 生态里仅次于 ripgrep / fzf / bat 的常驻项目

它的存在本身证明了一件事:同一品类不需要赢家通吃——lazygit 和 gitui 解的是不同程度的痛点,前者强在 git 兼容性和 rebase 体验,后者强在性能和无依赖。决定用哪个不是看哪个”更好”,是看你的仓库规模和你最常做什么操作。

学到什么

  1. CLI 包装层的瓶颈不在 UI,在子进程——这个洞见可以拷贝到很多场景:ripgrep 不调 grep,fd 不调 find,delta 不调 less。每多一次 fork+exec 就多一次进程启动 + 内存拷贝 + 文件描述符建立的开销,TUI 这种”几十毫秒就要刷一次”的场景被放大很多倍
  2. 直接绑定 C 库(libgit2 / libpng / libssl)有性能红利,也有跟不上主线的代价——是个永恒的权衡。git 主仓加了 sparse-checkout v2,libgit2 跟进可能要半年到一年
  3. TUI 工具的横向对比:不只是看 feature 列表,还要看底层架构选择——lazygit / gitui / btop 都是多面板单键,但工程路线完全不同
  4. Rust CLI 时代的复利:单二进制、无 runtime、cargo install 一行装好——这套体验本身就是采用率的引擎,对比 npm install 一堆 peerDependency 就能看出来差距

延伸阅读

  • 项目主页:extrawurst/gitui(README 里有完整 keybinding 表)
  • 性能对比文章:gitui vs lazygit benchmarks(Linux 内核仓库的实测数字就是从这里来的)
  • libgit2-rs 文档:git2-rs docs(理解 gitui 的底层 API;自己写 git 工具也用这个)
  • 作者博客:extrawurst.com(Stephan 写过几篇 gitui 设计回顾,讲为什么不走 git CLI 路线)
  • lazygit —— 同品类 Go 实现,对照阅读
  • ratatui —— gitui 用的 Rust TUI 框架(可能未建档)

关联

  • lazygit —— 同品类对照,Go + git 子进程 vs Rust + libgit2 的两条工程路线
  • btop —— 同样多面板单屏 TUI 设计哲学,但场景是系统监控而非 git
  • ripgrep —— 同样”绕开外部 CLI 直接做”的 Rust 工程模式(不调 grep)
  • delta —— diff 渲染端的 Rust 工具,常和 git TUI 串联使用
  • fzf —— gitui 内嵌的 fuzzy 搜索思想来源
  • bat —— 同样的 Rust 单二进制 CLI 哲学,常和 gitui 出现在同一份 dotfiles 里