Docs Library

Git 历史说明

系统解释 Git 如何记录历史、为什么提交历史是一个有向图,以及这对协作、恢复和变更审查意味着什么。

一句话理解

Git 的“历史”不是一串简单的文本日志,而是一组由提交对象连接起来的有向图。每个提交都记录了当前项目快照、作者信息、提交说明,以及它的父提交。

为什么 Git 历史这么重要

理解 Git,不只是记住 addcommitpush 这些命令,更关键的是知道这些命令如何改变历史图。只有看懂历史,你才真正能理解:

  • 为什么 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 通常就有机会把你带回过去的某个节点。也就是说,很多“丢了”的内容并不是马上消失,而是暂时失去了容易访问的名字。

这也是为什么在误操作后,第一反应应该是:

  1. 先停止继续做破坏性命令
  2. 查看 git reflog
  3. 确认你想回到哪个提交
  4. 再决定是新建分支、reset,还是 cherry-pick 回来

读历史时最常用的方式

git log --oneline --graph --decorate --all

这条命令能把分支、提交关系和合并结构更直观地展示出来,是理解历史图非常好的入口。

一个实用判断法

如果你不确定某个命令“危险不危险”,先问自己一句:

“它是在新增历史节点,还是在改写我当前分支指向的历史表达?”

通常来说:

  • 新增节点的操作更容易审计和恢复
  • 改写历史表达的操作更需要确认共享边界

继续学习建议

如果你已经理解这页内容,下一步建议继续看:

  1. refs and HEAD
  2. git merge
  3. git rebase
  4. reflog recovery

参考来源