Docs Library
Git 历史说明
系统解释 Git 如何记录历史、为什么提交历史是一个有向图,以及这对协作、恢复和变更审查意味着什么。
一句话理解
Git 的“历史”不是一串简单的文本日志,而是一组由提交对象连接起来的有向图。每个提交都记录了当前项目快照、作者信息、提交说明,以及它的父提交。
为什么 Git 历史这么重要
理解 Git,不只是记住 add、commit、push 这些命令,更关键的是知道这些命令如何改变历史图。只有看懂历史,你才真正能理解:
- 为什么
merge会产生汇合点 - 为什么
rebase会改写提交 ID - 为什么
revert是新增一个反向提交 - 为什么
reflog能在误操作后帮你找回状态
一个提交里到底有什么
根据官方书对 Git 对象模型的说明,一个提交至少包含这些关键信息:
- 一个指向 tree 对象的引用,用来表示该次提交对应的目录快照
- 一个或多个父提交
- 作者与提交者信息
- 提交时间和提交说明
这意味着提交历史本质上是在追踪“项目状态如何从一个节点演化到另一个节点”。
为什么它不是线性的
很多初学者会把 Git 历史想成 Excel 里的一列记录,但 Git 真正维护的是图结构。
普通提交
普通提交通常只有一个父提交,所以看起来像一条线不断向前。
merge 提交
当你把两个分支整合在一起时,merge commit 会有两个父提交,这就是为什么历史图会出现“汇合”。
root 提交
仓库最早的那个提交没有父提交,它是整张图的起点。
分支为什么只是“名字”
官方书反复强调,分支不是一份独立副本,而是一个指向某个提交的可移动引用。
这件事很关键,因为它解释了很多常见问题:
- 新建分支为什么几乎是瞬时的
- 删除分支为什么通常不会立刻删除提交对象
- fetch 为什么更新的是
origin/main这类远端跟踪引用
所以,很多 Git 操作并不是“复制整个项目”,而是在移动或新增这些指向提交的名字。
HEAD 在历史里扮演什么角色
HEAD 用来表示“你当前站在哪个位置上看历史”。
- 正常情况下,
HEAD指向当前分支名 - detached HEAD 时,
HEAD直接指向某个提交
理解这一点后,你会更容易明白为什么 checkout/switch 到某个提交后继续提交,会生成一段暂时没有分支名接住的历史。
常见命令如何影响历史
commit
创建一个新提交,把当前快照接到当前分支后面。
merge
把另一条历史线整合进来,通常产生一个有两个父提交的节点。
rebase
不是“编辑原提交”,而是把原来的改动重新应用到新的基底上,因此会产生一批新的提交对象。
reset
重点是移动分支引用和索引/工作区状态,它可能改变你“当前指向哪里”,但不会立刻从对象数据库里抹掉旧提交。
revert
通过一个新提交来抵消历史中的某个旧提交,而不是把旧提交删除。
为什么历史能帮助你恢复
只要对象还在、引用或 reflog 里还保留着线索,Git 通常就有机会把你带回过去的某个节点。也就是说,很多“丢了”的内容并不是马上消失,而是暂时失去了容易访问的名字。
这也是为什么在误操作后,第一反应应该是:
- 先停止继续做破坏性命令
- 查看
git reflog - 确认你想回到哪个提交
- 再决定是新建分支、reset,还是 cherry-pick 回来
读历史时最常用的方式
git log --oneline --graph --decorate --all
这条命令能把分支、提交关系和合并结构更直观地展示出来,是理解历史图非常好的入口。
一个实用判断法
如果你不确定某个命令“危险不危险”,先问自己一句:
“它是在新增历史节点,还是在改写我当前分支指向的历史表达?”
通常来说:
- 新增节点的操作更容易审计和恢复
- 改写历史表达的操作更需要确认共享边界
继续学习建议
如果你已经理解这页内容,下一步建议继续看:
refs and HEADgit mergegit rebasereflog recovery