Git 全景課 / 第二部分 分支與歷史 · M4

M4 分支:可移動的標籤

M3 說過,分支只是一個 41 bytes 的檔案。本單元看它怎樣動:提交時誰會往前走、切換時甚麼會改變,以及 HEAD 離開分支時要小心甚麼。
約 60 分鐘沙盒 7 個任務 · 小測 4 題先修:M3
學完本單元,你能夠:
  1. 建立、切換、列出和刪除分支;
  2. 在每個命令之後,預測 HEAD 和各分支標籤會指向哪個 commit;
  3. 分辨 HEAD 的兩種狀態,並在 detached HEAD 下不弄丟工作;
  4. 分清楚 branch 與 worktree、checkout 與 switch/restore。

1分支是一張會跟着走的標籤 A branch is a label that follows you

M3 的結論是:commit 一旦寫入就不會改變,而分支是 .git/refs/heads/ 裏一個寫着 commit 編號的小檔案。分支和標籤(tag)的分別只有一點:你在某條分支上提交時,那條分支會自動移到新的 commit。

提交前後的分支標籤 上排:main 和 feature 都指向 commit B,HEAD 指向 feature。下排:在 feature 上提交後,新增 commit C,feature 移到 C,main 留在 B,HEAD 仍然指向 feature。 提交前 A B main HEAD feature 在 feature 上 git commit 之後 C(新) main HEAD feature HEAD 指向 feature,所以「目前在 feature 上」。 新 commit C 的 parent 是 B。 feature 移到 C;main 不動;HEAD 仍指向 feature。
圖 1. 提交時只有一條分支會移動:HEAD 所指的那條。深色標籤是分支,白底框是 HEAD,藍色是新 commit。
預測規則 每次提交:① 新 commit 的 parent=HEAD 現在指着的 commit;② HEAD 所指的分支移到新 commit;③ 其他分支一律不動。記住這三步,任何分支圖都能預測。

2HEAD 的兩種狀態 Two states of HEAD

指向分支(一般情況)detached HEAD(直接指向 commit)
.git/HEAD 的內容ref: refs/heads/main一個 commit 編號
git status 顯示On branch mainHEAD detached at 1a2b3c4
提交時分支跟着往前走只有 HEAD 往前走,沒有分支記住新 commit
怎樣進入git switch maingit switch --detach <commit>、git checkout <編號或標籤>
用途日常工作查看舊版本、臨時實驗

detached HEAD 不是錯誤,只是「不在任何分支上」。它唯一的風險在第 5 節。

3切換分支時改變了甚麼 What switching changes

git switch feature 做了兩件事:

  1. 把 .git/HEAD 改成 ref: refs/heads/feature;
  2. 把工作目錄和暫存區換成 feature 所指 commit 的快照。檔案會在你眼前變成那個版本的樣子。

它不會複製任何東西,也不會新增任何物件。這正是 FAQ-001 的重點:一個資料夾(worktree)同一時間只能展示一條分支;要同時展示兩條,就要用 git worktree add 開第二個資料夾(M8)。

切換前先提交 如果工作目錄有未提交的修改,而切換會覆蓋到它們,Git 會拒絕切換並列出那些檔案。這時你可以先提交,或者用 git stash 暫時收起(M8)。沙盒為了專注於分支,每次提交都會自動修改並 add 檔案,所以不會遇到這種情況。

4沙盒:commit 圖 Sandbox: the commit graph

互動 commit 圖沙盒

輸入命令,看 HEAD 和分支標籤怎樣移動。每次提交,沙盒會在 notes.md 加一行並自動 add;圖上的編號都是真實的 SHA-1。按順序完成七個任務,每執行一條命令之前,先猜猜標籤會去哪裏。

應用線:

5detached HEAD:不要讓 commit 變孤兒 Detached HEAD and orphaned commits

在 detached HEAD 狀態下提交,新 commit 只由 HEAD 指着。一旦你切換回某條分支,就再沒有任何 ref 指向它:它還在物件資料庫裏,但從任何分支都找不到,git log 也不會列出它。這種 commit 叫做懸空 commit(dangling commit)。

Git 會在你離開時提醒你:

Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  9f3c2e1 試試另一種寫法

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> 9f3c2e1

照它說的做就能救回:git branch rescue 9f3c2e1 給那個 commit 貼上一張分支標籤。即使你錯過了這段提示,Git 也會在一段時間內(預設至少 30 天)保留記錄,可以用 git reflog 找回編號(M6)。

何時會不知不覺進入 detached HEAD 用 git checkout 加上 commit 編號或標籤名稱(例如 git checkout v1.0 想看舊版本);或者在 rebase 途中(M7)。看到 HEAD detached at … 時,先想清楚接下來要不要提交;要的話,先 git switch -c <新分支>。

6容易混淆的對照 Easily confused

branch 與 worktree

branchworktree
是甚麼指向 commit 的標籤(歷史裏的一個位置)磁碟上的一個資料夾(一張書桌)
數量可以有幾百條,成本幾乎為零每多一個就多一份工作檔案
切換git switch 改變這個資料夾展示的內容到另一個資料夾工作,兩邊同時存在

詳見 FAQ-001,M8 會實際操作 worktree。

checkout 與 switch/restore

舊版 Git 只有 git checkout,它一條命令做兩件很不同的事。2019 年起(Git 2.23),Git 把它拆成兩條意思清楚的命令:

想做的事舊寫法新寫法
切換分支git checkout featuregit switch feature
建立並切換git checkout -b featuregit switch -c feature
查看舊 commitgit checkout 1a2b3c4(悄悄進入 detached HEAD)git switch --detach 1a2b3c4(要你明確表態)
放棄檔案修改git checkout -- notes.mdgit restore notes.md(M1)

舊寫法仍然有效,網上大量答案也還在用,所以要看得懂;但自己寫時用新寫法,因為命令名字就說明了它會影響甚麼。

常見誤解
  • 「建立分支會自動切換過去。」不會。git branch feature 只建立標籤;要同時切換,用 git switch -c feature。
  • 「刪除分支會刪除它的 commit。」不會。只是撕掉標籤;commit 若沒有其他 ref 指向,就變成懸空 commit。所以 git branch -d 在分支未合併時會拒絕,要你用 -D 確認。
  • 「main 是特別的分支。」不是。它只是預設名字;Git 對它沒有任何特別待遇,「正本」的地位是團隊約定和保護規則給它的(M0、M11)。

7命令速查 Cheat sheet

命令作用
git branch列出分支,* 是目前所在的
git branch feature在 HEAD 處建立分支(不切換)
git branch fix 1a2b3c4在指定的 commit 建立分支
git switch feature切換到分支
git switch -c feature建立並切換
git switch --detach v1.0進入 detached HEAD 查看舊版本
git branch -d feature刪除已合併的分支(未合併會被拒絕)
git branch -D feature強制刪除(它的 commit 可能變成懸空)
git log --oneline --graph --all在終端機畫出所有分支的歷史
HEAD~1、main~2「往回一步」「main 往回兩步」:沿第一個 parent 往回數

8延伸練習 More practice

Learn Git Branching(Peter Cottle 等人製作,MIT 授權,有繁體中文介面)是一套以關卡形式練習分支的網站。完成本單元後,可以先做它主要關卡裏的入門系列,熟悉提交、分支和切換;合併和 rebase 的關卡留到 M5、M7 之後再做。

9小測 Quiz

1. main 和 feature 都指向 commit B,HEAD 指向 main。執行 git commit 之後:
2. git branch feature 之後,你在哪條分支上?
3. 你在 detached HEAD 下提交了一個 commit,然後 git switch main。那個 commit 怎樣了?
4. 想看 v1.0 時的檔案,又不想誤入 detached HEAD 而不自知,最清楚的命令是:

本單元重點 Summary

延伸閱讀