跳转到内容

Git 引用 (Refs)

在上一章中,我们了解到 Git 通过哈希值(40位的 SHA-1 字符串)来标识提交。但是,要让用户去记忆 a042389... 这样的哈希值简直是噩梦。

这就是 引用 (References,简称 Refs) 存在的意义。

引用本质上就是给提交哈希起的一个好记的名字,比如 mastermainv1.0

在 Git 的默认 files 后端中,松散引用 (loose refs) 通常存储为 .git/refs/ 目录下的文本文件。但为了节省 inode 和提高效率,Git 还会把大量引用合并到 .git/packed-refs 文件中(称为 packed refs)。此外,HEAD 这个特殊引用位于 .git/HEAD,并不在 .git/refs 目录内。

我们可以通过文件系统直接查看这些引用。

Terminal window
ls -F .git/refs/
Terminal window
heads/ (存放本地分支)
tags/ (存放标签)
remotes/ (存放远程分支)

当我们执行 git branch feature 时,Git 实际上只是在 .git/refs/heads/ 下创建了一个名为 feature 的文件,并将当前 Commit 的哈希值写入其中。

验证一下:

Terminal window
# 查看 master 分支指向的提交
cat .git/refs/heads/master
Terminal window
1a2b3c4d5e... (一个哈希值)
Terminal window
# 验证它是否与 git log 显示的一致
git log -1 --format=%H

所以,Git 的分支非常轻量。创建一个分支仅仅是写入一个很小的引用记录(通常就是一行对象 ID),无论你的项目有几百兆大,创建分支都是瞬间完成的。

标签存储在 .git/refs/tags/ 目录下。

  • 轻量标签 (Lightweight Tag):就像分支一样,文件内容直接就是 Commit 的哈希值。
  • 附注标签 (Annotated Tag):文件内容指向的是一个 Tag 对象(我们在上一章提到的第四种对象),而不是直接指向 Commit。

当你运行 git pushgit fetch 时,Git 会更新 .git/refs/remotes/ 下的文件。 例如,origin/main 分支的状态就保存在 .git/refs/remotes/origin/main 文件中。

HEAD 是一个非常特殊的引用,它决定了你的工作目录当前处于哪个版本。

如果你查看 .git/HEAD 文件,你会发现它通常不包含哈希值,而是指向另一个引用。这被称为 符号引用 (Symbolic Ref)

Terminal window
cat .git/HEAD
Terminal window
ref: refs/heads/master

这句话的意思是:“我现在处于 master 分支上”。当我们提交代码时,Git 会先解析 HEAD 找到 refs/heads/master,然后更新 refs/heads/master 指向新的 Commit 哈希。

当你检出一个具体的 Commit 哈希(而不是分支名)时:

Terminal window
git checkout 1a2b3c...

.git/HEAD 的内容会直接变成该哈希值:

Terminal window
cat .git/HEAD
Terminal window
1a2b3c...

这就叫 分离头指针 状态。此时 HEAD 不指向任何分支引用,因此新的提交不会更新任何分支。一旦切走,这些提交可能会被垃圾回收。

不过别太担心——Git 的 reflog 会记录 HEAD 的历史移动轨迹,默认保留 90 天。在这个窗口期内,你可以通过 git reflog 找回这些”丢失”的提交。

graph LR
HEAD[HEAD File] --> RefMaster[refs/heads/master]
HEAD -.-> RefFeature[refs/heads/feature]
RefMaster --> C1[Commit a1b2c3]
RefFeature --> C2[Commit d4e5f6]
subgraph .git/refs
RefMaster
RefFeature
end
subgraph .git directory
HEAD
end
classDef head fill:#e53e3e,stroke:#c53030,color:#fff
classDef branch fill:#3182ce,stroke:#2c5282,color:#fff
classDef commit fill:#4a5568,stroke:#2d3748,color:#fff
class HEAD head
class RefMaster,RefFeature branch
class C1,C2 commit
  • 分支 只是一个指向特定 Commit 的指针文件。
  • HEAD 是一个指向当前所在分支的指针。
  • 切换分支 (git checkout) 实际上就是修改 HEAD 文件的内容,并更新工作目录的文件。
  • 移动分支 (git reset) 实际上就是修改 .git/refs/heads/<branch> 文件中的哈希值。

理解了 Refs,你就理解了 Git 分支操作的物理本质。