Appearance
引用(References)
Git 的引用(References,简称 refs)是指向 Git 对象(通常是 Commit)的指针。引用让我们可以用人类可读的名称(如 main、HEAD)代替难以记忆的 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.0bash
# 查看所有标签
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:合并中对方分支的 HEADCHERRY_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: 修复 bugreflog 是"最后的安全网",即使误删分支或 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 字节的文件!