bigywater.tech
常见报错 · 22

Git You are in 'detached HEAD' state:切走时只有一条警告,游离提交 reflog 能找回,gc 之后就没了

“You are in ‘detached HEAD’ state” 吓到过很多人。它不是报错,但在这个状态下提交的东西如果没处理,会悄悄变成一个不属于任何分支的提交。这篇在本机把进入、提交、切走、找回、彻底丢失的过程都复现了一遍,看清楚哪一步才会真的丢东西。

0 个
切回 main 之后,包含游离提交 b8f4757 的分支个数
128
reflog 过期并 gc 之后再查那个提交的退出码(Not a valid object name)
4 种
本篇复现的会让 HEAD 游离的操作:checkout 提交、checkout 标签/远程分支、交互式变基、子模块 clone

这是什么状态

实测 · 本机执行输出
    $ cat .git/HEAD
      ref: refs/heads/main
    $ git checkout HEAD~1 2>&1 | head -20
      Note: switching to 'HEAD~1'.
      
      You are in 'detached HEAD' state. You can look around, make experimental
      changes and commit them, and you can discard any commits you make in this
      state without impacting any branches by switching back to a branch.
      
      If you want to create a new branch to retain commits you create, you may
      do so (now or later) by using -c with the switch command. Example:
      
        git switch -c <new-branch-name>
      
      Or undo this operation with:
      
        git switch -
      
      Turn off this advice by setting config variable advice.detachedHead to false
      
      HEAD is now at bf2ae14 commit 2
    $ cat .git/HEAD
      bf2ae14ce1a479b38adfc66f9ab5df8b2495889b
    $ git status | head -2
      HEAD detached at bf2ae14
      nothing to commit, working tree clean
    $ git branch
      * (HEAD detached at bf2ae14)
        main

HEAD 不指向分支名而是直接指向一个提交。git status 和 git branch 都会写出来。

什么时候会丢东西

实测 · 本机执行输出
    $ echo detached-work > d.txt && git add d.txt && git commit -qm "work in detached HEAD" && git log --oneline -3
      b8f4757 work in detached HEAD
      bf2ae14 commit 2
      082b0c4 commit 1
    $ git switch main 2>&1
      Warning: you are leaving 1 commit behind, not connected to
      any of your branches:
      
        b8f4757 work in detached HEAD
      
      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> b8f4757
      
      Switched to branch 'main'
    $ git log --oneline main
      a0d8828 commit 3
      bf2ae14 commit 2
      082b0c4 commit 1
    $ git branch -a --contains b8f4757 | wc -l | tr -d " "
      0
    (上一行是包含提交 b8f4757 的分支个数:0 表示没有任何分支能走到它)

在游离状态提交后切走,没有任何分支指向这个提交。切走时有一条警告,但很容易被忽略。

怎么找回

实测 · 本机执行输出
    $ git reflog -6
      a0d8828 HEAD@{0}: checkout: moving from b8f47573603260fa51d1587c0cf1aa31dbc1309f to main
      b8f4757 HEAD@{1}: commit: work in detached HEAD
      bf2ae14 HEAD@{2}: checkout: moving from main to HEAD~1
      a0d8828 HEAD@{3}: commit: commit 3
      bf2ae14 HEAD@{4}: commit: commit 2
      082b0c4 HEAD@{5}: commit (initial): commit 1
    $ git cat-file -t b8f4757
      commit
    $ git branch rescue b8f4757 && git log --oneline rescue -3
      b8f4757 work in detached HEAD
      bf2ae14 commit 2
      082b0c4 commit 1
    $ git branch -D rescue
      Deleted branch rescue (was b8f4757).
    $ git reflog expire --expire=now --all && git gc -q --prune=now; git cat-file -t b8f4757    (退出码 128)
      fatal: Not a valid object name b8f4757

reflog 里有哈希,git branch 一个名字就保住;强制过期并 gc 后才真的没有。

还有哪些操作会游离

实测 · 本机执行输出
    $ git checkout -q main && git checkout -q v1.0 && git status | head -1
      HEAD detached at v1.0
    $ git checkout -q main
    $ git clone -q repo clone && cd clone && git checkout -q origin/main && git status | head -1
      HEAD detached at origin/main
    $ git rebase -i HEAD~2 --exec "git branch | head -1" 2>&1 | grep -v "^Rebasing\|^Executing"
      * (no branch, rebasing main)
      * (no branch, rebasing main)
      Successfully rebased and updated refs/heads/main.
    $ git -c protocol.file.allow=always clone -q --recurse-submodules super super2 2>&1 | head -2; cd super2/sub && git status | head -1 && git branch
      HEAD detached at 88a869a
      * (HEAD detached at 88a869a)
        main
    $ cd super2/sub && git switch -q main && git status | head -1
      On branch main

checkout 标签、checkout 远程分支、交互式变基过程中、子模块默认状态。

怎么出来

实测 · 本机执行输出
    $ git switch -q main && git checkout -q HEAD~1 && git switch - 2>&1
      Previous HEAD position was bf2ae14 commit 2
      Switched to branch 'main'
    $ git checkout -q HEAD~1 && echo more > m.txt && git add m.txt && git commit -qm "kept work" && git switch -c feature-from-detached 2>&1 && git log --oneline -3 && git branch
      Switched to a new branch 'feature-from-detached'
      e08aa9e kept work
      bf2ae14 commit 2
      082b0c4 commit 1
      * feature-from-detached
        main
    $ git switch main -q && git merge --no-edit feature-from-detached 2>&1 | head -4; git log --oneline -4
      Merge made by the 'ort' strategy.
       m.txt | 1 +
       1 file changed, 1 insertion(+)
       create mode 100644 m.txt
      c36a635 Merge branch 'feature-from-detached'
      a0d8828 commit 3
      e08aa9e kept work
      bf2ae14 commit 2
    $ git switch --detach HEAD~0 2>&1 | head -2
      HEAD is now at c36a635 Merge branch 'feature-from-detached'

没提交就 switch 回分支;提交过先 switch -c 新分支。

分步演示

游离HEAD
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64;报错文本都是真实复现时原样捕获的(命令行工具统一用 LC_ALL=C 取英文原文,路径换成 );所有服务都是本机上自己起的、只监听 127.0.0.1,不访问外网;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 git 2.54.0(Apple Git-157);仓库都在临时目录里,用 GIT_CONFIG_GLOBAL 隔离了配置,提交时间固定,所以哈希可复现;子模块测试用本机路径并临时允许 file 协议(-c protocol.file.allow=always)
    • 没有验证:reflog 与 gc 的默认过期时间(只测了手动 expire=now 与 prune=now 的结果);cherry-pick / bisect 过程中的 HEAD 状态;git worktree 里的游离 HEAD;其他 git 版本里 Note 提示文字的差异;图形界面工具对该状态的显示
    京ICP备2026067502号-1京公网安备11010502063487号