Skip to content

引用(References)

Git 的引用(References,简称 refs)是指向 Git 对象(通常是 Commit)的指针。引用让我们可以用人类可读的名称(如 mainHEAD)代替难以记忆的 40 位 SHA-1 哈希值。

HEAD 是什么

HEAD 是 Git 中最重要的引用,它告诉 Git:你现在在哪里

HEAD 通常是符号引用

正常情况下,HEAD 不直接指向某个 Commit,而是指向当前分支:

bash
cat .git/HEAD
# ref: refs/heads/main

这意味着:HEAD → main → 某个 Commit

bash
# HEAD 指向 main 分支,main 指向最新提交
HEAD refs/heads/main abc123...

分离 HEAD 状态(Detached HEAD)

当你直接 checkout 到某个 Commit(而非分支)时,HEAD 直接指向该 Commit:

bash
git checkout abc123
# HEAD 处于"分离"状态

cat .git/HEAD
# abc123...(直接是哈希,不是分支引用)

此时如果你创建新提交,不会更新任何分支,这些提交可能会"丢失"。

bash
# 解决方案:从当前位置创建分支
git checkout -b new-branch
# 或者使用新语法
git switch -c new-branch

分支引用(refs/heads/)

本地分支存储在 .git/refs/heads/ 目录下,每个分支就是一个文件:

.git/refs/heads/
├── main          # 内容:abc123...(最新提交的 SHA-1)
├── develop       # 内容:def456...
└── feature/login # 内容:ghi789...
bash
# 查看分支引用内容
cat .git/refs/heads/main
# abc123456789...

# 等价于
git rev-parse main
# abc123456789...

分支的本质:一个分支就是一个包含 Commit SHA-1 的文本文件。每次提交时,当前分支文件的内容更新为最新 Commit 的 SHA-1。这就是为什么 Git 的分支操作如此轻量快速。

bash
# 查看所有分支及其指向
git branch -v
# * main    abc123 feat: 添加登录功能
#   develop def456 fix: 修复样式问题

远程引用(refs/remotes/)

远程追踪分支存储在 .git/refs/remotes/ 下:

.git/refs/remotes/
└── origin/
    ├── main     # 记录上次与远程同步时,origin/main 的位置
    ├── develop
    └── HEAD     # 远程的默认分支
bash
# 查看远程追踪分支
git branch -r
# origin/main
# origin/develop

# 查看所有分支(本地 + 远程)
git branch -a

远程引用的特点:

  • 你不能直接在远程分支上提交
  • 它们在 git fetch 时自动更新
  • 表示"上次与远程通信时,远程分支的位置"

标签引用(refs/tags/)

标签存储在 .git/refs/tags/ 下:

.git/refs/tags/
├── v1.0.0    # 轻量标签:内容是 Commit SHA-1
├── v1.1.0    # 附注标签:内容是 Tag 对象 SHA-1
└── v2.0.0
bash
# 查看所有标签
git tag

# 查看标签指向的对象
git cat-file -t v1.0.0   # commit 或 tag
git cat-file -p v1.0.0

与分支不同,标签不会随着新提交移动。标签是永久标记。

符号引用(Symbolic Reference)

符号引用不直接存储 SHA-1,而是存储另一个引用的名称。HEAD 就是最典型的符号引用。

bash
# HEAD 是符号引用
cat .git/HEAD
# ref: refs/heads/main   ← 这就是符号引用

# 读取符号引用指向的实际值
git symbolic-ref HEAD
# refs/heads/main

# 修改符号引用(切换分支的底层操作)
git symbolic-ref HEAD refs/heads/develop

其他符号引用:

  • FETCH_HEAD:最近一次 git fetch 的结果
  • ORIG_HEAD:危险操作前 HEAD 的位置(用于恢复)
  • MERGE_HEAD:合并中对方分支的 HEAD
  • CHERRY_PICK_HEAD:cherry-pick 中的源提交

HEAD~n 与 HEAD^n 的区别

这两种语法都用于引用历史提交,但含义不同:

~(波浪号):第几代祖先

~n 表示沿着第一父提交向上追溯 n 代:

A ← B ← C ← D ← HEAD

HEAD~0 = HEAD = D
HEAD~1 = C
HEAD~2 = B
HEAD~3 = A

^(脱字符):第几个父提交

对于合并提交,有多个父提交。^n 选择第 n 个父提交:

      A
     / \
    B   C
     \ /
      M  ← HEAD(合并提交)
  • HEAD^ = HEAD^1 = B(第一父提交,merge 时当前分支的最新提交)
  • HEAD^2 = C(第二父提交,被合并分支的最新提交)
  • HEAD~1 = HEAD^ = B

组合使用

bash
# 合并提交的第二父的父提交
git show HEAD^2~3

# 等价写法
git show HEAD^^^2     # 3个^ 等于 ~3(都是第一父提交),最后 ^2 选第二父

实用示例

bash
# 查看上一个提交
git show HEAD~1
git show HEAD^

# 回退到上两个提交(保留工作区)
git reset --soft HEAD~2

# 查看合并提交的两个父提交
git log HEAD^1 -1   # 第一父提交(当前分支)
git log HEAD^2 -1   # 第二父提交(被合并分支)

引用日志(reflog)

Git 还维护着每个引用的变更历史(reflog),记录了引用的所有移动:

bash
# 查看 HEAD 的变更历史
git reflog
# abc123 HEAD@{0}: commit: feat: 新功能
# def456 HEAD@{1}: checkout: moving from develop to main
# ghi789 HEAD@{2}: commit: fix: 修复 bug

reflog 是"最后的安全网",即使误删分支或 reset --hard,也能通过 reflog 找回。

打包引用(packed-refs)

当仓库有大量引用时,Git 会将它们打包到 .git/packed-refs 文件中,提高查找效率:

bash
cat .git/packed-refs
# # pack-refs with: peeled fully-peeled sorted
# abc123... refs/heads/main
# def456... refs/heads/develop
# ghi789... refs/tags/v1.0.0
# ^jkl012...     ← 附注标签实际指向的 commit

总结

引用类型位置特点
HEAD.git/HEAD当前位置,通常是符号引用
分支.git/refs/heads/随提交自动移动
远程追踪.git/refs/remotes/fetch 时更新,只读
标签.git/refs/tags/永久标记,不自动移动

理解引用机制后,你会发现 Git 的分支、HEAD 移动等操作都只是在修改这些简单的文本文件。这也是 Git 分支操作如此高效的原因——创建分支只需写入 41 字节的文件!