學習 Git - 什麼是 HEAD?


Posted by Calon on 2022-05-20

我們在使用 git log 的時候都可以看到 (HEAD -> 分支) 這樣的訊息,而究竟這個 HEAD 究竟是什麼?
HEAD 簡單來說就是指當前所在的分支,在學習 Git (7) - 分支有提到可以將分支想像成是一張便利貼貼在當前分支最後一個 commit 上,而在這邊可以將 HEAD 也想像成是另一張便利貼貼在分支便利貼的旁邊。
那 Git 是如何知道現在在哪個分支下?其實在 .git 目錄底下有個 HEAD 檔案,而這個檔案就是紀錄著 HEAD 內容,我使用 cat 指令來印出 HEAD 檔案內容:

$ cat .git/HEAD
ref: refs/heads/main

從印出來的內容來看應該是指著 main 這個分支,我們順著印出來的內容繼續檢視 refs/heads/main 這個檔案:

$ cat .git/refs/heads/main
485e562ee0d47d372c277207329674d0980dbe2c

內容看起來像是 commit 的 SHA-1 值,這次我們先確認所在分支後再用 git log 來確認是是不是所想的那樣:

$ git branch
  develop
* main
$ git log --oneline
485e562 (HEAD -> main) Merge branch 'develop' into main
595b530 新增 test.txt
4444f10 (develop) 新增 work2.txt
6f5eefa 新增 work1.txt
ccf65d1 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init

refs/heads/main 的內容的確是指 main 分支上最新的一筆 commit ,也就是說 main 分支的便利貼現在貼在(指向)485e562 這個 commit,而 HEAD 便利貼貼在(指向)main 分支上。

那如果我們將 .git/HEAD 檔案裡面的內容改成 refs/heads/develop 呢?

$ echo 'ref: refs/heads/develop' > .git/HEAD
$ cat .git/HEAD
ref: refs/heads/develop

修改完之後使用 git branchgit log 來確認分支位置和 HEAD:

$ git branch
* develop
  main
$ git log --oneline
4444f10 (HEAD -> develop) 新增 work2.txt
6f5eefa 新增 work1.txt
ccf65d1 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init

分支位置和 HEAD 的指向都變成 develop 了。
最後我們再切回去 main 分支看 .git/HEAD 檔案裡面的內容會不會變回 ref: refs/heads/master:

$ git checkout main
Switched to branch main
$ cat .git/HEAD
ref: refs/heads/main

又變回 main 分支了。

斷頭狀態(detached HEAD)

我們在學習 Git (6) - 修改 commit 紀錄 part 3:Rebase使用 git rebase 修改 commit 紀錄時可以發現執行訊息有出現 detached HEAD 很多次,而 detached HEAD 也就是斷頭狀態又是什麼?
我們在上面介紹 HEAD 時說過 HEAD 是指向「現在所在的分支位置」,正常情況下分支會指向一個 commit,而 HEAD 會指向某個分支,但在有些情況下 HEAD 沒有指向任何一個分支時的 HEAD 狀態就稱作 detached HEAD。

datched HEAD 可能發生在:

  1. 使用 git rebase 的時候修改 commit 紀錄的時候就是不斷地處在 detached HEAD 狀態。
  2. 使用 git checkout 跳回過去某個 commit 時,例如跳回當前 HEAD 的前一個 commit,HEAD 也可以寫成 @,所以在這邊也可以寫成 @^:
    ```bash
    $ git checkout @^
    Note: switching to 'origin/main'.

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

Or undo this operation with:

git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 595b530 新增 test.txt


這訊息是告訴我們現在處於 detached HEAD 的狀態,如果想要恢復到原本的狀態:
```bash
$ git switch -

而 detached HEAD 這個狀態就只是告訴我們 HEAD 沒有指向任何一個分支而已,我們可以像平常使用 Git 一樣來操作:

$ touch detached-head-test.txt
$ git add detached-head-test.txt
$ git commit -m '新增 detached-head-test.txt'
[detached HEAD 043106b] 新增 detached-head-test.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 detached-head-test.txt

使用 git log 來檢視一下紀錄:

$ git log --oneline
043106b (HEAD) 新增 detached-head-test.txt
595b530 新增 test.txt
ccf65d1 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init

有成功提交上去了,不過可以發現 HEAD 並沒有指向任何分支,也就是說如果我現在切回 main 分支的話,在沒有記住 043106b (HEAD) 新增 detached-head-test.txt 這筆 commit 的 SHA-1 值的狀況下,之後就不容易找到,而一段時間之後 Git 就會把它回收掉,那如果想要保留這些 commit,我們可以依照剛才使用 git checkout 造成斷頭狀態時給的指令 git switch -c 來創一個分支給它:

$ git switch -c no-head
Switched to a new branch 'no-head'

git log 檢視:

043106b (HEAD -> no-head) 新增 detached-head-test.txt
595b530 新增 test.txt
ccf65d1 新增 rule 3
c8699fa 新增 rule2.txt
7cf2baa 新增 rules.txt 內容
deb6d18 init

現在 HEAD 有指向 no-head 這個分支了。

  1. 切換到遠端分支的時候,遠端的介紹在後面會做介紹,而為什麼切到遠端分支也會造成 detached HEAD 的原因是因為 detached HEAD 準確來說是 HEAD 沒有指向本地的某個分支,所以當切換到遠端分支的時候就會造成 detached HEAD 狀態。

離開 detached HEAD 狀態

既然知道了 detached HEAD 的狀態成因,所以我們只要將 HEAD 重新指向任何分支就可以解決此問題,例如我們把它指向回 main 分支:

$ git checkout main

參考資料
  • 高見龍,《為你自己學 Git》

#Git







Related Posts

CS50 Internet Primer

CS50 Internet Primer

箭頭函式(arrow function)

箭頭函式(arrow function)

[General] 在 Windows Terminal 安裝 vim編輯器懶人包

[General] 在 Windows Terminal 安裝 vim編輯器懶人包


Comments