Skip to content

引用原理

深入理解 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(^ 前缀)

查找引用的优先级:

  1. 先查找 .git/refs/ 目录中的文件(松散引用)
  2. 再查找 .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/HEADref: refs/heads/<branch>
HEAD(分离).git/HEAD直接是 commit SHA-1
packed-refs.git/packed-refs多个引用的汇总文件

理解引用原理后,Git 的分支操作、checkout、reset 等命令都变得更加清晰——它们本质上就是在创建、移动或删除这些引用文件