Git Internals

Git 提交图与历史表达

理解为什么 Git 历史本质上是提交图,而不是简单时间线,以及 merge、rebase 如何重新表达这张图。

一句话理解

Git 保存的是提交图。线性日志只是这张图的一种阅读方式,不是 Git 底层真正的唯一结构。

1. 为什么“历史图”比“提交列表”更准确

如果把 Git 历史只理解成列表,你会很难解释:

  • merge commit 为什么会有两个父节点
  • rebase 为什么会生成一组新的提交 ID
  • 为什么同一段改动能以不同图形结构被表达

把它看成图之后,这些现象就自然很多。

2. 普通提交如何形成一条线

大多数提交只有一个父提交,因此从阅读体验上会像一条线:

A -> B -> C -> D

这只是“图在这个局部看起来像线”,并不代表 Git 只有线性结构。

3. merge commit 表达了什么

merge commit 往往有两个父节点。它告诉 Git:

  • 这里有两条历史线
  • 它们在这个提交处汇合

所以 merge 的价值不只是“把代码拼起来”,还在于把“曾经并行开发过”这个事实保留下来。

4. rebase 为什么看起来更线性

rebase 的做法不是修改旧提交,而是:

  1. 取出一组提交表达的改动
  2. 换到新的基底上
  3. 重新生成一组提交对象

结果就是:

  • 图结构被重新表达
  • 提交 ID 会变化
  • 阅读上通常更线性

5. merge 和 rebase 的真正差异

它们都可以完成“把两边改动整合起来”这件事,但历史表达不同。

merge

  • 保留分叉与汇合
  • 历史图更忠实于协作过程
  • 已共享历史通常更安全

rebase

  • 让一组提交接到新的基底后面
  • 历史看起来更直
  • 适合整理尚未共享的个人分支

6. 为什么 commit ID 会跟着历史结构变化

因为 commit 对象里不仅保存内容快照,还保存父提交关系。只要父节点不同,commit 对象就不是同一个对象,ID 也会跟着变化。

这就是 rebase 会“改写历史”的底层原因。

7. 为什么看图能帮助你判断风险

当你知道历史是图,就更容易判断:

  • 某个 merge commit 是不是一个真实的汇合点
  • 当前分支是不是只比主线多出一小段未共享提交
  • reset 或 force push 会不会让别人依赖的路径突然消失

8. 用图来重新理解常见命令

git log --graph

它不是在“制造图”,而是在把已经存在的提交父子关系可视化。

git merge

它常常是在图里新增一个有多个父节点的提交。

git rebase

它会把旧改动重新表达成另一段提交链。

git cherry-pick

它会把某个提交表达的改动单独复制成一条新的提交节点。

常见误区

rebase 只是把提交“挪一挪”

不准确。更准确的说法是:rebase 基于新的父链重新生成提交对象。

merge 比 rebase 更“脏”

这不是底层结论,而是阅读偏好和协作策略差异。merge 更强调保留真实分叉结构,rebase 更强调线性表达。

一个实用判断原则

如果你关心“团队真实怎样并行协作过”,merge 的图更直观;如果你关心“合并前把自己的一段提交整理清楚”,rebase 更合适。