“You are in ‘detached HEAD’ state” 吓到过很多人。它不是报错,但在这个状态下提交的东西如果没处理,会悄悄变成一个不属于任何分支的提交。这篇在本机把进入、提交、切走、找回、彻底丢失的过程都复现了一遍,看清楚哪一步才会真的丢东西。
$ 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)
mainHEAD 不指向分支名而是直接指向一个提交。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 b8f4757reflog 里有哈希,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 maincheckout 标签、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 新分支。