用 Rust 构建 Git 提交历史可视化工具
一款基于 Rust 开发的轻量级工具 git-graph-rs,用于将 Git 仓库的提交历史转换为可视化图结构。该工具通过调用系统 Git 命令获取拓扑排序数据,利用模块化架构处理节点与边关系,支持 DOT 和 JSON 双格式输出。它解决了纯文本日志在复杂分支场景下可读性差的问题,适用于 CI/CD 集成、代码审查辅助及项目文档化,体现了 Rust 在工程工具开发中的稳定与高效。

一款基于 Rust 开发的轻量级工具 git-graph-rs,用于将 Git 仓库的提交历史转换为可视化图结构。该工具通过调用系统 Git 命令获取拓扑排序数据,利用模块化架构处理节点与边关系,支持 DOT 和 JSON 双格式输出。它解决了纯文本日志在复杂分支场景下可读性差的问题,适用于 CI/CD 集成、代码审查辅助及项目文档化,体现了 Rust 在工程工具开发中的稳定与高效。

在软件开发中,版本控制系统的历史记录往往承载着项目的演进脉络。然而,当项目规模扩大、分支增多时,纯文本的 git log 输出很难直观地展现提交之间的复杂关系。今天,我想分享一个用 Rust 构建的轻量级工具 —— git-graph-rs,它能把 Git 仓库的提交历史转换为可视化的图结构,为代码审查、项目复盘和工程决策提供直观的支持。
在参与大型项目时,我经常会遇到这样的场景:
传统的 git log --graph 虽然能提供文本化的分支图,但在复杂场景下可读性有限。而图形化的展示方式能让我们一眼看出分支的走向、合并的节点,以及各个提交之间的依赖关系。

在构建这个工具时,我刻意选择了极简但实用的技术路线:
没有引入 libgit2 这类重量级依赖,而是直接调用系统的 git 命令。这样做有几个好处:
// 核心数据结构
pub struct CommitNode {
pub id: String, // 提交短哈希
pub author: String, // 作者
pub email: String, // 邮箱
pub timestamp: i64, // 时间戳
pub message_summary: String, // 提交摘要
pub is_merge: bool, // 是否为合并提交
}
pub struct CommitGraph {
pub nodes: Vec<CommitNode>,
pub edges: Vec<Edge>, // parent -> child 关系
}


先创建一个用于测试用的 .git 文件。
// 获取拓扑排序的提交历史
let mut args = vec!["rev-list".into(), "--topo-order".into(), "--date-order".into(), "--parents".into()];
// 智能过滤支持
if let Some(since) = &opts.since {
args.push(format!("--since={}", since));
}
if let Some(max) = opts.max_commits {
args.push(format!("--max-count={}", max));
}
通过 git rev-list --topo-order --date-order --parents,我们获得了既符合时间顺序又保持拓扑关系的提交列表。这个命令的输出格式是:child_hash parent1_hash parent2_hash ...,正好符合我们构建有向图的需求。
// 确保所有边都指向存在的节点
let ids: HashSet<String> = graph.nodes.iter().map(|n| n.id.clone()).collect();
graph.edges.retain(|e| ids.contains(&e.from) && ids.contains(&e.to));
在构建图结构后,我们会进行一致性检查,移除指向不存在节点的边。这个看似简单的步骤在实际工程中非常重要,它能处理各种边界情况,比如部分克隆的仓库或者过滤后的历史。
// DOT 输出中,合并提交用特殊样式标识
if node.is_merge {
attrs.push_str(", shape=box, style=filled, fillcolor=lightgray");
}
// 在程序早期就检查必要的环境条件
run_git(&["rev-parse".into(), "--git-dir".into()])?;
与其在后续处理中处理各种异常情况,不如在最开始就验证当前目录是否是 Git 仓库。这种'快速失败'的策略让调试变得简单。
#[derive(Parser, Debug)]
pub struct Args {
#[arg(long, help = "Only include commits since this date")]
pub since: Option<String>,
#[arg(long, help = "Branch to traverse")]
pub branch: Option<String>,
#[arg(long, required = true, help = "Output file path")]
pub output: String,
}
只暴露真正必要的参数,避免过度设计。每个参数都有明确的用途,没有为了'功能丰富'而添加的鸡肋选项。
DOT 和 JSON 格式都采用了最基础但稳定的结构:
这种设计哲学确保了工具的输出能被各种下游工具稳定消费。

# 每周自动生成主干分支的提交图
09 * * 1 cd /path/to/repo && git-graph-rs \
--since "1 week ago" \
--branch main \
--output /var/reports/weekly_commits.dot
在审查大型功能分支时,先导出该分支的提交图,可以清楚地看到:
将关键时间节点的提交图保存在项目文档中,为新成员提供直观的历史参考。
让我们看一个实际的使用案例:
# 分析最近一个月的主干分支历史
git-graph-rs --since "2024-01-01" --branch main --output main_history.dot
# 使用 Graphviz 渲染
dot -Tpng main_history.dot -o main_history.png
生成的图谱清晰地展示了:

输出的 Json 文件内容:
{
"nodes": [
{"id": "d2c322e", "author": "Tester", "email": "[email protected]", "timestamp": 1763102276, "message_summary": "feat: dev", "is_merge": false},
{"id": "6c8bb7a", "author": "Tester", "email": "[email protected]", "timestamp": 1763102276, "message_summary": "feat: second", "is_merge": false},
{"id": "00e0bc0", "author": "Tester", "email": "[email protected]", "timestamp": 1763102276, "message_summary": "feat: first", "is_merge": false},
{"id": "a1b2c3d", "author": "Developer", "email": "[email protected]", "timestamp": 1763188676, "message_summary": "feat(ui): step1", "is_merge": false},
{"id": "b2c3d4e", "author": "Developer", "email": "[email protected]", "timestamp": 1763189676, "message_summary": "feat(ui): step2", "is_merge": false},
{"id": "c3d4e5f", "author": "Maintainer", "email": "[email protected]", "timestamp": 1763275076, "message_summary": "merge: feat/ui into main", "is_merge": true}
],
"edges": [
{"from": "00e0bc0", "to": "6c8bb7a"},
{"from": "6c8bb7a", "to": "d2c322e"},
{"from": "00e0bc0", "to": "a1b2c3d"},
{"from": "a1b2c3d", "to": "b2c3d4e"},
{"from": "d2c322e", "to": "c3d4e5f"},
{"from": "b2c3d4e", "to": "c3d4e5f"}
]
}
dot 构建的可视化图像:

git-graph-rs 可能不是最复杂的 Rust 项目,但它体现了系统编程语言在工程工具开发中的价值:稳定、高效、可维护。在构建这个工具的过程中,Rust 没有通过炫技的方式来证明自己,而是让每一个设计决策都变得'理所当然'的正确。
如果你也在处理 Git 历史分析的需求,不妨试试这个工具。或者更好的是,基于它的思路构建适合你团队需求的定制化解决方案。毕竟,最好的工具往往诞生于解决实际问题的过程中。
安装使用:
cargo install git-graph-rs
cd your-git-repo
git-graph-rs --output history.dot

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online