Commit 与 DAG¶
很多人第一次接触 Git 提交历史时,会把它想成“提交列表”。
这个理解能勉强应付 git log,但一旦遇到分支和 merge,就不够了。
更准确的说法是:
Git 的提交历史是一个有向无环图,也就是 DAG。
commit 里到底有什么¶
一个 commit 最核心的信息包括:
- 指向根 tree 的对象 ID;
- 一个或多个父提交;
- 作者和提交者信息;
- 时间戳;
- 提交说明。
commit 本身不直接保存“分支名”。
分支名是 refs 系统的工作,不是 commit 对象的一部分。
为什么历史是图,不是线¶
如果每个 commit 都只有一个父提交,那历史看起来像一条链。
但 Git 允许:
- 从旧提交上新开分支;
- 合并另一条分支;
- 形成拥有两个父提交的 merge commit。
一旦 merge commit 出现,历史就不再是线性列表,而是图结构。
这也是为什么很多 Git 功能必须思考“图关系”,而不是只思考“前后顺序”。
什么是 DAG¶
DAG 是 Directed Acyclic Graph,中文叫有向无环图。
拆开看:
- 有向:提交关系是有方向的,子提交指向父提交;
- 无环:不会出现某个提交最终又指回自己祖先后的自己。
这两个性质让 Git 能可靠地做:
- 祖先判断;
- 最近公共祖先搜索;
- 快进判断;
- 历史遍历;
- merge base 计算。
branch 为什么这么轻¶
一旦你理解 commit 图,就会发现 branch 根本不是“复制一份项目”。
branch 本质只是一个 ref,指向图中的某个 commit。
新建分支时,不是复制对象库,而只是新增一个名字。
这也是 Git 分支为什么非常轻量的原因。
HEAD 在这里扮演什么角色¶
HEAD 表示“当前检出的目标”。
通常情况下,HEAD 会符号引用某个分支,比如:
ref: refs/heads/main
这意味着当前工作区和提交行为都跟着 main 走。
如果你 checkout 到某个 commit 而不是某个分支,HEAD 就会进入 detached 状态。
log 为什么通常按第一父链看¶
你平时看到的很多历史输出,默认是沿着父提交链往回走。
在简单场景里,这很像链表遍历。
但只要出现 merge commit,历史就不再只有一个父节点。
因此:
- 有些命令看第一父链;
- 有些命令要看所有父边;
- merge 时更要在图里找最近公共祖先。
最近公共祖先为什么重要¶
假设你有两条分支:
- ours
- theirs
想把它们合并,不能只比较两个分支头。
你还必须知道:它们共同从哪里分叉出来。
这个共同祖先就是 merge 的基准点。
没有它,你就无法准确判断:
- 哪些改动只有一边做了;
- 哪些改动两边都做了;
- 哪些改动发生了冲突。
在 python-git-reproduction 中的意义¶
这个项目里:
commit-tree创建 commit 对象;commit更新当前分支 ref;log沿历史读提交;merge通过 DAG 找最近公共祖先;- 分支、标签和
HEAD都是围绕 commit 图在移动。
所以理解 commit 与 DAG,不只是学 Git,也是在学这个项目的整体骨架。