Git Internals

Git 中的引用与 HEAD

把分支、标签、远端跟踪引用和 HEAD 放到同一个模型里,理解 Git 如何用名字指向提交。

一句话理解

提交对象是历史节点,而引用是指向这些节点的名字。HEAD 则表示你当前正站在哪个名字或哪个提交上。

1. 为什么“引用”比命令参数更值得先理解

很多 Git 命令本质上都在做两件事:

  1. 创建新对象
  2. 移动引用

如果只记命令表面行为,就容易在 reset、rebase、checkout、reflog 这些场景里迷路。

2. 什么是引用

引用可以理解成一个更容易记忆的入口名。常见例子有:

  • main
  • feature/login
  • v2.0.0
  • origin/main

它们背后最终都落到某个提交对象上。

3. 分支为什么只是“可移动引用”

分支不是独立仓库副本,也不是一条被复制出来的历史线。它只是一个可以随着新提交向前移动的名字。

这解释了三个常见现象:

  • 新建分支很快,因为只是在新增一个引用
  • 切换分支很快,因为只是让 HEAD 去跟随另一个引用
  • 删除分支不代表提交马上消失,因为对象可能还被别的引用或 reflog 接着

4. 标签和分支有什么不同

都属于引用,但习惯上用途不同:

  • 分支通常会继续向前移动
  • 标签更像把某个位置固定成一个稳定名字

所以分支更偏“协作中的当前位置”,标签更偏“发布或里程碑标记”。

5. HEAD 到底是什么

在日常正常状态下,HEAD 通常是一个符号引用。它不是直接指向提交,而是先指向当前分支,再由分支指向提交。

这就是为什么:

  • 切换分支会让 HEAD 跟随新的分支名
  • commit 后当前分支向前移动,HEAD 看起来也就跟着前进
  • reset 会改变当前分支与 HEAD 共同指向的位置

6. 什么是 detached HEAD

当 HEAD 不再跟随分支名,而是直接指向某个提交时,就进入了 detached HEAD。

它不是仓库损坏,而是说明你暂时站在“一个具体提交”上,而不是“一个可移动的分支名”上。

这时如果继续提交:

  • 新提交仍然会被创建
  • 但不会自动挂到某个分支名下面
  • 如果想保留,就应该尽快新建分支把这段历史接住

7. 远端跟踪引用为什么重要

mainorigin/main 不是一回事:

  • main 是你的本地分支
  • origin/main 是本地记录的远端状态

git fetch 更新的是后者。也正因为这两者分开,Git 才能让你先观察远端变化,再决定是否 merge、rebase 或 reset。

8. reflog 为什么常常能救命

reflog 本质上记录的是引用移动历史。很多恢复操作之所以能成功,不是因为 Git 知道你“后悔了”,而是因为它还记得某个引用之前指向过哪里。

这也是为什么:

  • reset 过头经常还能找回来
  • rebase 出错时也常能定位旧位置
  • 删除分支后如果 reflog 还在,往往依然有恢复机会

常见误区

HEAD 就是当前提交

更准确的说法是:HEAD 常常通过当前分支间接定位到当前提交。只有在 detached HEAD 时,它才直接指向提交。

删除分支等于删除提交

不一定。删除的是“名字”,不是立即销毁对象。只要还有别的引用或 reflog 接着,这些提交通常还在。

一个最值得记住的结论

很多 Git 故障感,实际上都是“名字变了”而不是“对象坏了”。把对象和引用分开理解,你对 branch、HEAD、reflog、reset 的判断会稳定很多。