淺入 Git:detached HEAD

留言

目錄

  1. 前言
  2. 路人甲發生的情境
  3. 此狀態有何用
  4. 資料來源

在 Git 的世界中,想回到過去,也許你會用 git checkout 跳至某個過去的 commit,但 Git 會提醒你正在「detached HEAD」狀態。那 detached HEAD 是什麼?它能做什麼?要怎麼離開 detached HEAD 狀態?

前言

在之前的 深入 Git:HEAD refs 有談到 HEAD 通常會指向當前分支,而當前分支通常會指向該分支頂端的 commit (也就是該分支最新的 commit)。不過再某些情況下,會發生 HEAD 不是指向分支,而是指向某個 commit,而這個狀態的 HEAD 就被稱為「detached HEAD」。

下面會用以下 Git 歷史紀錄作為範例來說明 detached HEAD。本專案內有四個檔案,分別是 abcd,我為每個檔案分別建立了一個 commit:

1
2
3
4
5
6
7
8
$ ls
a b c d

$ git log --oneline --graph --decorate
* 71e5770 (HEAD -> master, origin/master) feat: d
* c3a31ea feat: c
* b757c26 feat: b
* 3b7be17 feat: a

路人甲發生的情境

下面是路人甲發生的情境,讓我們來看這個情境發生了什麼?

現在 HEAD 指向 master 分支,而 master 分支指向名為 e2f2a4 的 commit:

1
2
3
4
5
6
7
8
9
10
$ git log --oneline --graph --decorate
* 71e5770 (HEAD -> master, origin/master) feat: d
* c3a31ea feat: c
* b757c26 feat: b
* 3b7be17 feat: a

$ cat .git/HEAD
ref: refs/heads/master
$ cat .git/refs/heads/master
71e57702f208a7c463a2b5cd069ea47cdbed9eba

路人甲可能想回到過去的 commit,於是使用了 git checkout <commit> 跳至指定的 commit,假設要 checkout 至名為 b757c26 的 commit 就會看到以下訊息:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git checkout b757c26
Note: checking out 'b757c26'.

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 performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at b757c26 feat: b

假設路人甲很少注意看 Git 提供的訊息,所以忽略了「detached HEAD」的提醒訊息。

路人甲只想確定自己是不是回到過去,於是執行 git log 來察看,的確看到 HEAD 指向 b757c26 這個 commit:

1
2
3
$ git log --oneline --graph --decorate
* b757c26 (HEAD) feat: b
* 3b7be17 feat: a

所以心滿意足的開始基於 7e347a4 這個 commit 開始建立兩個新的 commit:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ echo "test1" > test1
$ git add test1
$ git commit -m "feat: test1"
[detached HEAD 3936d06] feat: test1
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 test1

$ echo "test2" > test2
$ git add test2
$ git commit -m "feat: test2"
[detached HEAD 845992a] feat: test2
1 file changed, 1 insertion(+)
create mode 100644 test2

所以現在 git log 變成這樣:

1
2
3
4
5
$ git log --oneline --graph --decorate
* 845992a (HEAD) feat: test2
* 9fd0924 feat: test1
* b757c26 feat: b
* 3b7be17 feat: a

當路人甲建立完 commit 後,突然想回到之前的 master 分支,所以執行 git checkout master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git checkout master
Warning: you are leaving 2 commits behind, not connected to
any of your branches:

845992a feat: test2
9fd0924 feat: test1

If you want to keep them by creating a new branch, this may be a good time
to do so with:

git branch <new-branch-name> 845992a

Switched to branch 'master'
Your branch is up to date with 'origin/master'.

回到 master 分支後,路人甲看 git log 變成下面這樣馬上傻眼,心想:「剛剛的那兩個 commit 呢?怎麼不見了!怎麼辦啊!還找的回來嗎?」:

1
2
3
4
5
$ git log --oneline --graph --decorate
* 71e5770 (HEAD -> master, origin/master) feat: d
* c3a31ea feat: c
* b757c26 feat: b
* 3b7be17 feat: a

好,看到這個故事,你應該會發現,其實 Git 一直不停的在輸出訊息提醒你:

  • 在 checkout 至某個 commit 時,會提醒你正在「detached HEAD」狀態
  • 在「detached HEAD」狀態建立 commit 時,會像這樣提醒你:[detached HEAD 845992a] feat: test2
  • 在「detached HEAD」狀態建立 commit 後,checkout 回分支時,會像這樣提醒你:Warning: you are leaving 2 commits behind

所以看輸出訊息很重要!

我曾經就是那個不看輸出訊息的路人甲 XD

此狀態有何用

那「detached HEAD」狀態到底能做什麼?

如果在 detached HEAD 狀態時建立的 commit 是實驗性的,那的確可以直接 checkout 至任何分支,把那些 commit 都丟棄 (但 Git 不會馬上刪除,會自動等到 Git GC 來回收,所以還救的回來),這代表你放棄了在 detached HEAD 狀態下所做的任何 commit,而且這些操作都不會影響任何分支。

那如果是像路人甲的情境那樣,你想保留那些 commit,那可以在該 commit 上建立 ref,讓該 ref 指向該 commit,ref 可以是分支或 tag。

建立 ref 有很多種方式,下面會介紹幾種處理的方式。

資料來源

分享:

討論區