2026-06-11 · commit · parent · DAG · branch · HEAD · detached HEAD
parent 串成一張歷史圖,以及 branch 和 HEAD 這兩個東西——它們聽起來很玄,實際上只是各存著一個 hash 的小指標。看懂這章,你會發現 branch、merge、reset、rebase 全是「在這張圖上挪指標」而已。
回想第 2 章:commit 的 hash 是從它全部內容(根 tree、parent、作者、時間、訊息)算出來的。這帶來一個關鍵性質——commit 一旦建立就不可變。你沒辦法「修改」一個 commit;任何改動都會算出不同的 hash,那其實是另一個新 commit。舊的那顆原封不動還在那裡。
每個 commit 都記著自己的 parent(上一顆 commit 的 hash)。順著 parent 一路往回,就是專案的歷史線。因為每顆 commit 都「指向過去」,這些箭頭永遠朝後、不會形成迴圈,於是整段歷史是一張 DAG(有向無環圖,Directed Acyclic Graph):
amend、rebase)其實都是造一批新 commit、再把指標移過去,舊 commit 仍短暫留在物件庫裡(這正是第 9 章 reflog 能救命的原因)。
很多人以為「分支」是一份檔案的複本,或一條被框起來的 commit 鏈。都不是。一條分支的真身,就是第 2 章看過的 .git/refs/heads/ 底下一個小文字檔,檔名是分支名,內容只有一行——它所指向那顆 commit 的 40 字元 hash:
$ cat .git/refs/heads/main → a1b2c3d4…(就這一行,40 字元)
所以「分支」輕到不行——它不擁有任何 commit,只是一張寫著 hash 的便利貼,貼在某顆 commit 上。一顆 commit 上可以同時貼好幾張(好幾條分支指到同一顆),也可以一張都沒有。
那麼,「我新 commit 時,分支指標怎麼自己往前移?」——靠的是下一節的 HEAD。
如果說 branch 是貼在 commit 上的便利貼,那 HEAD 就是貼在便利貼上的便利貼——它記錄「我現在在哪一條分支上」。.git/HEAD 通常不是一個 hash,而是一行指向某條分支的引用:
$ cat .git/HEAD → ref: refs/heads/main
這一層「間接」是整個機制的關鍵。HEAD → 分支 → commit 三段接力,讓 git commit 時發生這串連鎖反應:
main),再找到它指的 commit ——這就是新 commit 的 parent。main,於是「自動」跟著前進了。當你 git checkout <某顆 commit 的 hash>(而不是分支名)時,HEAD 會直接指向那顆 commit,跳過中間的分支那一層。這就是 detached HEAD(分離的 HEAD)。它本身不是錯誤——你可以四處看、可以試做幾顆 commit;但要小心:
git switch -c <新分支名> 立刻貼一張便利貼上去。(真的忘了?第 9 章的 reflog 還有機會救。)
下一節給你一個可以親手玩的圖譜,把這三件事——新 commit 讓分支前移、HEAD 跟著走、detached 時的孤兒 commit——一次看個清楚。
按按鈕下 Git 指令,看 commit(灰圓)、分支(紫標籤)、HEAD(珊瑚標籤)怎麼在圖上移動。重點觀察:commit 時,HEAD 所在的那條分支往前跳一格,HEAD 黏著它一起走。試試開 feature 分支、各自 commit 看歷史分岔;再試 checkout 舊 commit 進入 detached HEAD。
git commit → ② 按 git checkout A 進 detached → ③ 再 git commit(做一顆沒有分支的 commit)→ ④ 按 git switch main。注意第 ③ 步那顆 commit 在第 ④ 步後就沒有任何分支指著了——它正是上面警告說的「無人認領」commit。
| 概念 | 一句話記住 |
|---|---|
| commit 不可變 | hash 涵蓋全部內容,動不了舊 commit;「改歷史」=造新 commit + 移指標 |
| parent → DAG | 每顆 commit 指向過去的 parent,串成有向無環圖;合併 commit 有多個 parent |
| branch | 一個只存 40 字元 hash 的小檔,指向某顆 commit;開分支幾乎零成本 |
| HEAD | 記「我在哪條分支」,通常是 ref: refs/heads/… 這層間接 |
| commit 時 | 分支指標前移一格,HEAD 黏著分支一起走 |
| detached HEAD | HEAD 直接指 commit、跳過分支;此時做的 commit 無分支認領,易遺失 |
init / add / commit / log / diff 這些每天在用的指令,究竟在這張圖和三大區域上做了什麼。