Appearance
引用原理
深入理解 Git 引用的存储和工作原理,有助于理解分支操作、HEAD 移动等高层概念的本质。
引用是指向 commit 的文件
Git 中的引用(References,简称 refs)是存储在 .git/refs/ 目录下的文本文件,内容是一个 SHA-1 哈希值:
bash
# 查看分支引用的内容
cat .git/refs/heads/main
# a1b2c3d4e5f6789012345678901234567890abcd
# 查看标签引用的内容
cat .git/refs/tags/v1.0.0
# d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1vw
# 等价的 git 命令
git rev-parse main
git rev-parse v1.0.0引用系统的设计理念: 用人类可读的名称(如 main)代替 40 位 SHA-1,但底层就是一个简单的文本文件。
分支的本质
分支的本质是一个可移动的指针(指向某个 Commit 的文件):
bash
# 创建分支 = 在 refs/heads/ 下创建一个文件
git branch new-feature
# 等价于:
echo "$(git rev-parse HEAD)" > .git/refs/heads/new-feature
# 提交时,分支指针自动前进
# Git 将新提交的 SHA-1 写入当前分支的引用文件分支为什么这么轻量: 创建一个分支只需写入 41 字节(40位SHA-1 + 换行符),而不是复制文件。
HEAD 的分离状态(Detached HEAD)
正常状态
HEAD 是一个符号引用,指向当前分支的引用文件:
bash
cat .git/HEAD
# ref: refs/heads/main
# HEAD → refs/heads/main → abc123...分离 HEAD 状态
当你 checkout 到某个具体的 commit 时,HEAD 直接包含 SHA-1,不再指向分支:
bash
git checkout abc123 # 进入分离 HEAD 状态
cat .git/HEAD
# abc123456789012345678901234567890123456789
# (直接是 SHA-1,没有 ref: 前缀)分离 HEAD 的风险:
在分离 HEAD 状态下创建的提交:
abc ← def ← ghi ← HEAD(分离状态,没有分支指向这里)
切换回 main:
abc ← def ← ghi ← 这些提交变得不可达!(没有引用指向它们)
↑
main
在 GC 运行后,ghi 等提交会被清理避免提交丢失:
bash
# 在分离 HEAD 下提交前,先创建分支
git checkout -b rescue-branch
# 或者发现后立即创建分支
git branch recover-commits HEAD符号引用的实现
符号引用(Symbolic Reference)是指向另一个引用的引用:
bash
# 查看 HEAD 是否是符号引用
git symbolic-ref HEAD
# refs/heads/main(如果是符号引用)
# fatal: ref HEAD is not a symbolic ref(如果是分离 HEAD)
# 修改符号引用(切换分支的底层操作)
git symbolic-ref HEAD refs/heads/develop远程的 HEAD 也是符号引用:
bash
cat .git/refs/remotes/origin/HEAD
# ref: refs/remotes/origin/main
# (指示远程仓库的默认分支)packed-refs 优化
当仓库有大量引用时,逐个读取文件效率低下。Git 会将引用打包到 packed-refs 文件:
bash
# 手动打包引用
git pack-refs --all
# 查看打包结果
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(^ 前缀)查找引用的优先级:
- 先查找
.git/refs/目录中的文件(松散引用) - 再查找
.git/packed-refs文件(打包引用)
引用的命名空间
refs/
├── heads/ 本地分支
│ └── main
├── remotes/ 远程追踪分支
│ └── origin/
│ └── main
├── tags/ 标签
│ └── v1.0.0
├── stash stash 的引用
└── notes/ git notes 的引用特殊引用(位于 .git/ 根目录):
HEAD 当前位置
FETCH_HEAD 最近 fetch 的结果
ORIG_HEAD 危险操作前的 HEAD
MERGE_HEAD 合并时对方分支的 HEAD
CHERRY_PICK_HEAD cherry-pick 时源提交
REBASE_HEAD rebase 过程中的当前提交底层 git 命令
bash
# 查看引用指向的 SHA-1
git rev-parse HEAD
git rev-parse main
git rev-parse refs/heads/main
# 查看所有引用
git show-ref
# 验证引用
git show-ref --verify refs/heads/main
# 更新引用(底层操作)
git update-ref refs/heads/main abc123
# 删除引用(底层操作)
git update-ref -d refs/heads/old-branch总结
| 概念 | 存储位置 | 内容 |
|---|---|---|
| 本地分支 | .git/refs/heads/ | 一个 commit SHA-1 |
| 远程追踪分支 | .git/refs/remotes/ | 一个 commit SHA-1 |
| 标签 | .git/refs/tags/ | commit SHA-1 或 tag SHA-1 |
| HEAD(正常) | .git/HEAD | ref: refs/heads/<branch> |
| HEAD(分离) | .git/HEAD | 直接是 commit SHA-1 |
| packed-refs | .git/packed-refs | 多个引用的汇总文件 |
理解引用原理后,Git 的分支操作、checkout、reset 等命令都变得更加清晰——它们本质上就是在创建、移动或删除这些引用文件。