Git 底层原理系列 · 第8讲 — HEAD 与 detached HEAD

Git 底层原理系列 · 第8讲 --- HEAD 与 detached HEAD

⏱️ 预计阅读时间:14 分钟

目录

  • [📚 学习导航](#📚 学习导航 "#-%E5%AD%A6%E4%B9%A0%E5%AF%BC%E8%88%AA")
  • [⚡ 认知冲突](#⚡ 认知冲突 "#-%E8%AE%A4%E7%9F%A5%E5%86%B2%E7%AA%81")
  • [1 HEAD 的本质](#1 HEAD 的本质 "#1-head-%E7%9A%84%E6%9C%AC%E8%B4%A8")
  • [2 Attached HEAD:在分支上](#2 Attached HEAD:在分支上 "#2-attached-head%E5%9C%A8%E5%88%86%E6%94%AF%E4%B8%8A")
  • [3 Detached HEAD:不在分支上](#3 Detached HEAD:不在分支上 "#3-detached-head%E4%B8%8D%E5%9C%A8%E5%88%86%E6%94%AF%E4%B8%8A")
  • [4 Detached HEAD 下 commit 为什么"丢失"?](#4 Detached HEAD 下 commit 为什么"丢失"? "#4-detached-head-%E4%B8%8B-commit-%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%A2%E5%A4%B1")
  • [5 如何安全地在 Detached HEAD 下工作](#5 如何安全地在 Detached HEAD 下工作 "#5-%E5%A6%82%E4%BD%95%E5%AE%89%E5%85%A8%E5%9C%B0%E5%9C%A8-detached-head-%E4%B8%8B%E5%B7%A5%E4%BD%9C")
  • [6 ORIG_HEAD 和特殊引用](#6 ORIG_HEAD 和特殊引用 "#6-orig_head-%E5%92%8C%E7%89%B9%E6%AE%8A%E5%BC%95%E7%94%A8")
  • [7 动手实验:感受 Detached HEAD](#7 动手实验:感受 Detached HEAD "#7-%E5%8A%A8%E6%89%8B%E5%AE%9E%E9%AA%8C%E6%84%9F%E5%8F%97-detached-head")
  • 总结
  • 自测卡片

📚 学习导航

项目 内容
前置知识 第6讲:分支与引用
核心问题 Q1: HEAD 是什么? Q2: Detached HEAD 为什么危险? Q3: Detached HEAD 有什么实际用途?
预计收获 彻底理解 HEAD 的工作原理;安全地使用 detached HEAD

⚡ 认知冲突

你以为 detached HEAD 是一个"错误状态"?

实际上 detached HEAD 是一个非常有用(但也容易误用)的状态。Git 的很多核心操作------比如 git rebase -igit bisect------都在 detached HEAD 下工作。它不是错误,而是"指向了 commit 而非分支"的状态。


1 HEAD 的本质

HEAD 是一个指针,它告诉 Git"你现在在哪里"。它的内容很简单:

bash 复制代码
cat .git/HEAD
# 形式一:ref: refs/heads/main    ← 在分支 main 上
# 形式二:a1b2c3d4e5f6a7b8...    ← 直接指向某个 commit(detached)

HEAD 决定了三件事:

markdown 复制代码
1. 下一次 commit 的 parent
   → 新 commit 的 parent = $(git rev-parse HEAD)

2. 工作区的内容
   → checkout 时把 HEAD 指向的 commit 的 tree 解压到工作区

3. 当前在哪个分支上(git branch 的输出)

2 Attached HEAD:在分支上

这是"正常"状态------HEAD 指向一个分支,分支指向一个 commit:

bash 复制代码
HEAD → refs/heads/main → commit abc

在分支上时 git commit 的流程

bash 复制代码
# 1. git write-tree → 从 Index 创建 tree
# 2. git commit-tree TREE -p $(git rev-parse HEAD) -m "msg"
# 3. git update-ref refs/heads/main NEW_COMMIT
#    ↑ HEAD 指向 refs/heads/main,所以自动更新了 main

即:git commit → 新 commit → update-ref refs/heads/main → HEAD 不动(分支动了)

在分支上时 git checkout 的流程

bash 复制代码
git checkout feature
# 1. 读取 refs/heads/feature → commit hash
# 2. 更新 .git/HEAD → "ref: refs/heads/feature"
# 3. 解压 feature 的 tree 到工作区

3 Detached HEAD:不在分支上

HEAD 直接指向一个 commit:

sql 复制代码
HEAD → commit abc(没有经过任何分支)

进入 Detached HEAD 的方式

bash 复制代码
# 1. checkout 一个 commit hash
git checkout a1b2c3d4

# 2. checkout 一个 tag
git checkout v1.0

# 3. checkout 一个相对引用
git checkout HEAD~3

# 4. checkout 一个远程分支
git checkout origin/main

# 5. 某些命令内部进入(rebase -i, bisect 等)
git rebase -i HEAD~3  # rebase 过程中进入 detached HEAD

Detached HEAD 下 git commit 的流程

bash 复制代码
# 1. git write-tree → 创建 tree
# 2. git commit-tree TREE -p $(git rev-parse HEAD) -m "msg"
# 3. 没有 update-ref!因为 HEAD 不指向任何分支
#    HEAD 直接更新为指向新 commit

即:git commit → 新 commit → HEAD 指向新 commit(没有分支被更新)


4 Detached HEAD 下 commit 为什么"丢失"?

丢失的根本原因

bash 复制代码
# 在 detached HEAD 下创建了两个 commit
git checkout v1.0
# → HEAD is now at a1b2c3...

echo "change" > file.txt
git add file.txt && git commit -m "fix 1"
# → HEAD → d4e5f6(新 commit)

echo "more changes" > file.txt
git add file.txt && git commit -m "fix 2"
# → HEAD → g7h8i9(新 commit,parent=d4e5f6)

# 现在切换到 main
git checkout main
# → HEAD 指向 main,但 g7h8i9 和 d4e5f6 没有被任何引用指向!

此时 commit 图:

css 复制代码
a1b2c3 (v1.0) ── d4e5f6 ── g7h8i9
                               ↑ HEAD 刚才在这里
                               
main ── C1 ── C2 ── C3
↑ HEAD 现在在这里

d4e5f6g7h8i9 没有任何分支指向它们。它们成为"悬空对象"(dangling commits)。

悬空对象的后续

bash 复制代码
# 查看悬空对象
git fsck --lost-found
# → dangling commit d4e5f6...
# → dangling commit g7h8i9...

# 它们不会立即消失
# 直到 git gc 执行时才会被清理
# 默认保留 2 周(gc.reflogExpire)

5 如何安全地在 Detached HEAD 下工作

方法一:创建分支

bash 复制代码
# 在切换之前创建分支
git checkout v1.0
git switch -c hotfix-v1.0
# 或
git checkout -b hotfix-v1.0

# 现在 HEAD → refs/heads/hotfix-v1.0 → commit
# commit 不会丢失

方法二:在切换之前创建分支

bash 复制代码
git checkout v1.0
# ... 创建了两个 commit ...
git branch hotfix-v1.0  # 不切换,只创建分支指向当前 HEAD
# 或
git switch -c hotfix-v1.0  # 创建并切换到新分支

# 现在这两个 commit 被 hotfix-v1.0 引用了

方法三:用 reflog 找回丢失的 commit

bash 复制代码
# 如果不小心切走了
git checkout main

# 找回刚才在 detached HEAD 下创建的 commit
git reflog
# → g7h8i9 HEAD@{0}: commit: fix 2
# → d4e5f6 HEAD@{1}: commit: fix 1
# → a1b2c3 HEAD@{2}: checkout: moving from main to v1.0

# 创建分支指向它
git branch recovered g7h8i9

6 ORIG_HEAD 和特殊引用

Git 有一些特殊的引用,它们只存在于内存中或临时文件中:

bash 复制代码
# ORIG_HEAD:危险操作前的 HEAD 备份
git merge feature
# → Git 在合并前把 HEAD 保存到 ORIG_HEAD

# 如果合并出了问题
git reset --hard ORIG_HEAD
# 回到合并前的状态
特殊引用 何时创建 用途
ORIG_HEAD merge、rebase、reset 前 撤销危险操作
FETCH_HEAD git fetch 记录从远程获取的分支信息
MERGE_HEAD merge 进行中 记录被合并分支的 commit
CHERRY_PICK_HEAD cherry-pick 进行中 记录被 cherry-pick 的 commit
BISECT_HEAD bisect 进行中 记录 bisect 的当前 commit

7 动手实验:感受 Detached HEAD

bash 复制代码
mkdir ~/sandbox/detached-lab && cd ~/sandbox/detached-lab
git init
echo "v1" > file.txt && git add . && git commit -m "v1"
echo "v2" > file.txt && git add . && git commit -m "v2"
echo "v3" > file.txt && git add . && git commit -m "v3"
git tag v1.0 HEAD~2

# 进入 detached HEAD
git checkout v1.0
git log --oneline  # 发现只有 v1

# 在 detached HEAD 下创建 commit
echo "hotfix" > file.txt && git add . && git commit -m "hotfix on v1"

# 观察状态
git log --oneline
git branch -a  # 没有分支指向新 commit

# 切走看看
git checkout main
git log --all --oneline  # hotfix commit 还在吗?
# 不在了(git log 不显示悬空对象)

# 用 reflog 找回
git reflog
git branch recovered HEAD@{1}
git log recovered --oneline  # 找回来了!

总结

状态 HEAD 内容 git commit 行为 commit 安全性 适用场景
Attached ref: refs/heads/xxx 更新分支引用 ✅ 永远安全 日常开发
Detached commit hash 只移动 HEAD ⚠️ 可能丢失 临时查看、rebase、bisect

自测卡片

Q1:HEAD 文件里存了什么?

A: 两种可能:① ref: refs/heads/main(在分支上)② 一个 commit hash(detached)。HEAD 告诉 Git"你在哪里"。
Q2:为什么 detached HEAD 下 commit 会丢失?

A: 因为新 commit 没有更新任何分支引用(HEAD 不指向分支)。切换到其他地方后,新 commit 没有任何引用指向它,变成悬空对象。
Q3:如何找回 detached HEAD 下丢失的 commit?

A: 用 git reflog 找到 commit 的 hash,然后用 git branch <name> <hash> 创建分支指向它。Reflog 记录了 HEAD 曾经指向过的所有位置。
Q4:什么场景需要主动使用 detached HEAD?

A: ① git rebase -i(交互式变基)② git bisect(二分查找引入 bug 的 commit)③ 临时查看历史版本并做实验性修改 ④ checkout tag 查看某个版本。


第8讲完。下一讲:重置与还原 --- git reset / git revert / git restore 的底层原理。

相关推荐
洋仔3 小时前
Git 底层原理系列 · 第4讲 — `git add` 与 `git commit` 底层做了什么
git·开源
猫咪老师QAQ5 小时前
基于 Git Flow 的团队协作与发布流程实践
git
caicai_xiaobai5 小时前
分享一个访问Git Hub的好方法
git
心中有国也有家5 小时前
从零上手 CANN 学习中心:像逛技术便利店一样学昇腾
学习·算法·开源
Joy T6 小时前
【Web3】跨链资金池与消息路由:CCIP 智能合约集成实战与权限收束
git·web3·node·智能合约·hardhat
openFuyao6 小时前
以开源之力,突破多样化算力困局——openFuyao开源一周年背后的故事
人工智能·云原生·开源·openfuyao·多样化算力·集群软件
難釋懷7 小时前
Nginx虚拟主机
git·nginx·github
500847 小时前
ATC 做了什么:从 ONNX 到 .om
分布式·架构·开源·wpf·开源鸿蒙