bigywater.tech
Git · 27

HEAD 与 detached HEAD

HEAD 到底是什么?detached HEAD 又是怎么回事,在上面提交会怎样?这篇直接看 .git/HEAD 文件在各种状态下的内容,再看 reflog 如何帮你找回 detached 上的提交,最后认识 ORIG_HEAD、MERGE_HEAD 等几个特殊文件。

2 种形式
.git/HEAD 要么是 ref: refs/heads/<分支>,要么直接是 40 位 hash(detached)
1 个提交
从 detached HEAD 切回 main 时 git 警告“丢下 1 个不属于任何分支的提交”,reflog 能把它找回来
4 个文件
ORIG_HEAD、MERGE_HEAD、CHERRY_PICK_HEAD、FETCH_HEAD,只在对应操作期间或之后出现

HEAD 是什么

实测 · 本机执行输出
  $ cat .git/HEAD; git symbolic-ref HEAD; git rev-parse HEAD | cut -c1-12; cat .git/refs/heads/main | cut -c1-12
  ref: refs/heads/main
  refs/heads/main
  e79e434670c8
  e79e434670c8

.git/HEAD 在分支上是 ref: refs/heads/main 这样的符号引用,真正的提交 hash 存在 .git/refs/heads/main 里;HEAD 本身并不存提交。

detached

实测 · 本机执行输出
  $ git checkout -q 9867bddbd4ae45702c6ed062bec552b684c96d4d; cat .git/HEAD | cut -c1-12; git symbolic-ref HEAD 2>&1; git status | head -2; git branch
  9867bddbd4ae
  fatal: ref HEAD is not a symbolic ref
  HEAD detached at 9867bdd
  nothing to commit, working tree clean
  * (HEAD detached at 9867bdd)
    main

git checkout <commit> 之后 HEAD 里直接是 hash,git symbolic-ref HEAD 报错“不是符号引用”,git status 提示 detached。这时 HEAD 不再跟着任何分支。

在 detached 上提交

实测 · 本机执行输出
  $ git log --oneline --all --graph; git branch --contains HEAD 2>&1
  * 690e7c4 detached 上的提交
  | * e79e434 提交 3
  |/
  * 9867bdd 提交 2
  * 88a540f 提交 1
  * (HEAD detached from 9867bdd)
  切回 main:git 提醒你正在丢下一个提交
  $ git checkout main 2>&1
  Warning: you are leaving 1 commit behind, not connected to
  any of your branches:
  
    690e7c4 detached 上的提交
  
  If you want to keep it by creating a new branch, this may be a good time
  to do so with:
  
   git branch <new-branch-name> 690e7c4
  
  Switched to branch 'main'
  $ git branch --contains 690e7c485315d1a71ac3e10a948539a48f64a368; echo "(上面没有输出:没有任何分支包含它)"; git reflog | head -5 | cut -c9-
  (上面没有输出:没有任何分支包含它)
  HEAD@{0}: checkout: moving from 690e7c485315d1a71ac3e10a948539a48f64a368 to main
  HEAD@{1}: commit: detached 上的提交
  HEAD@{2}: checkout: moving from main to 9867bddbd4ae45702c6ed062bec552b684c96d4d
  HEAD@{3}: commit: 提交 3
  HEAD@{4}: commit: 提交 2

新提交只有 HEAD 指着它,不属于任何分支。切走时 git 会警告,并建议 git branch <名字> <hash>。如果错过警告,git branch --contains 查不到它。

用 reflog 找回

实测 · 本机执行输出
  $ git switch -c rescued 690e7c485315d1a71ac3e10a948539a48f64a368 2>&1; git log --oneline rescued
  Switched to a new branch 'rescued'
  690e7c4 detached 上的提交
  9867bdd 提交 2
  88a540f 提交 1

reflog 记录了 HEAD 的每一次移动,其中包括这个提交的 hash;用它起一个分支就找回来了。这是 reflog 最典型的用途(见后面 reflog 一篇)。

特殊引用

实测 · 本机执行输出
  $ git checkout -q main; ls .git | grep -E "^(ORIG_HEAD|FETCH_HEAD|MERGE_HEAD|CHERRY_PICK_HEAD|REVERT_HEAD|REBASE_HEAD)$" || echo "(现在一个都没有)"
  (现在一个都没有)
  $ git reset -q --hard HEAD~1; ls .git | grep -E "_HEAD$"; git rev-parse ORIG_HEAD | cut -c1-12; git reset -q --hard ORIG_HEAD
  ORIG_HEAD
  e79e434670c8
  $ git merge side 2>&1 | head -3; ls .git | grep -E "_HEAD$"; cat .git/MERGE_HEAD | cut -c1-12; git merge --abort; ls .git | grep -E "MERGE_HEAD" || echo "(abort 之后 MERGE_HEAD 没了)"
  Auto-merging a.txt
  CONFLICT (content): Merge conflict in a.txt
  Automatic merge failed; fix conflicts and then commit the result.
  MERGE_HEAD
  ORIG_HEAD
  6aea679fd5ec
  (abort 之后 MERGE_HEAD 没了)
  $ git cherry-pick side 2>&1 | head -2; ls .git | grep -E "_HEAD$"; git cherry-pick --abort
  Auto-merging a.txt
  CONFLICT (content): Merge conflict in a.txt
  CHERRY_PICK_HEAD
  ORIG_HEAD
  $ git remote add o /work/l23 2>/dev/null; git fetch -q o 2>&1; ls .git | grep -E "FETCH_HEAD"
  FETCH_HEAD
  FETCH_HEAD 的前两行(hash 前 12 位 / 是否可合并 / 来源):
    e79e434670c8 not-for-merge branch 'main'
    690e7c485315 not-for-merge branch 'rescued'

ORIG_HEAD(危险操作之前 HEAD 的位置)、MERGE_HEAD(正在合并的提交)、CHERRY_PICK_HEAD(cherry-pick 冲突)、FETCH_HEAD(最近一次 fetch)都是 .git 里的普通文件,命令结束或中止后有的消失,有的保留。

未出生的分支

实测 · 本机执行输出
  $ cat .git/HEAD; git status | head -3; git rev-parse HEAD 2>&1 | head -1; git log 2>&1 | head -1
  ref: refs/heads/main
  On branch main
  
  No commits yet
  fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree.
  fatal: your current branch 'main' does not have any commits yet
  $ git symbolic-ref HEAD refs/heads/其它名字; cat .git/HEAD; git status | head -2
  ref: refs/heads/其它名字
  On branch 其它名字
  $ git checkout --orphan fresh 2>&1; cat .git/HEAD; git status | head -3; git log 2>&1 | head -1; ls
  Switched to a new branch 'fresh'
  ref: refs/heads/fresh
  On branch fresh
  
  No commits yet
  fatal: your current branch 'fresh' does not have any commits yet
  a.txt

HEAD 可以指向一个还没有任何提交的分支名:新仓库、git symbolic-ref HEAD refs/heads/xxx、git checkout --orphan 都是这样,git log 和 git rev-parse HEAD 会报“还没有提交”。

分步演示

HEAD 与 detached HEAD
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。所有输出都来自真实命令;FETCH_HEAD 实验用本机另一个目录当远程;hash 因作者与时间固定而可复现(detached 上的提交 690e7c4 等)
    • 没有验证:git worktree 下每个工作树各自的 HEAD(后面有 worktree 一篇);rebase 期间的 REBASE_HEAD 与 .git/rebase-merge 目录(见 rebase 一篇);bisect 期间的 BISECT_HEAD;reflog 的过期(见 reflog 一篇);git switch --detach 与 git checkout --detach 的差别
    京ICP备2026067502号-1京公网安备11010502063487号