首頁/版本管理

Git 實戰系列:Worktree

2025年05月02日 版本管理 Git Worktree 版本控制 AI 協作

沒有 worktree 得靠 stash 兩頭跑,有 worktree 可以兩個資料夾同時開著

退款功能還在 feature/order-refund 上開發到一半,畫面開著 dev server,資料庫接的是測試用的假資料,還有一堆檔案沒 commit。這時候 PM 敲你:main 上結帳頁的稅額算錯了,要你馬上看一下。

以前遇到這種事,標準流程是 git stash,切到 main,看完、修完,再切回來,git stash pop。三、五分鐘的事,做起來卻很煩:stash 有時候會跟你切過去那邊的檔案衝突,dev server 因為切分支重新載入一次,如果兩個分支的 package.json 不一樣,還得重新 npm install。看完 main 的問題回來,偶爾還會忘記自己 stash 過東西,隔天才想起來。

git worktree 解決的就是這件事:同一個 repo,可以同時有好幾個工作目錄,各自 checkout 不同分支,互不干擾。

沒有 worktree 之前:stash、切分支、再切回來

流程大概長這樣:

git stash                              # 手上的修改先收起來
git checkout main                      # 切過去看 main
# ...看完、可能還順手修了一版...
git checkout feature/order-refund      # 切回來
git stash pop                          # 修改拿回來,祈禱不要衝突

這個流程本身沒有錯,git 也是設計成這樣用的。問題是它把「同時處理兩件事」硬擠成「輪流處理兩件事」,而且每次切換都有成本:

  • stash 可能衝突。 尤其你在 main 上也順手改了幾行,兩邊剛好動到同一個檔案。

  • 開發環境要重新暖機。 dev server 的 file watcher 重新掃一次、有時候直接掛掉;node_modules 版本不同的話還要重灌。

  • 狀態容易忘記。 stash 完去忙別的事,過一陣子回來已經不記得自己 stash 過什麼,git stash list 裡堆了好幾層。

worktree 不是取代這個流程,是讓你根本不用做這個流程——兩個分支各自待在各自的資料夾裡,你要哪個開哪個。

worktree 是什麼

一個 git repo 只有一份 .git,裡面存著所有的 commit、物件(object)和分支資訊。平常你 git checkout 切分支,動的其實是同一個工作目錄裡的檔案內容和 .git 裡那個唯一的 HEAD 指標。

git worktree add 做的事情是:在旁邊另外開一個資料夾,這個資料夾也是一份完整、可運作的工作目錄,checkout 到你指定的分支,但它跟原本的工作目錄共用同一份 .git,不是重新 clone 一份出來。

worktree 的結構:一份 .git,多個工作目錄

共用的是 commit 歷史、物件、分支這些「repo 本體」的資料;不共用的是工作目錄裡實際的檔案,包括每個資料夾自己的 node_modules.env、IDE 設定這些沒進版控的東西。這點很直覺:你在 order-refund-hotfix/ 改了一個檔案並 commit,order-refund/ 那邊 git log 馬上看得到這筆 commit(因為 .git 是共用的),但 order-refund-hotfix/ 資料夾裡多裝的套件,order-refund/ 不會自動有。

實際操作

在既有的 repo 旁邊,另開一個工作目錄看 main:

git worktree add ../order-refund-hotfix main

這樣會在上層目錄建一個 order-refund-hotfix/ 資料夾,裡面已經 checkout 好 main,可以直接 cd 進去跑 dev server、跑測試,跟原本 feature/order-refund 那邊的工作目錄完全獨立。

如果是要開一個新分支來修 bug,順便建 worktree,一行搞定:

git worktree add -b hotfix/tax-calc ../order-refund-hotfix main

看完、改完之後,確認資料夾裡沒有殘留的未 commit 修改,就可以移除:

git worktree remove ../order-refund-hotfix

想確認目前 repo 底下開了哪些 worktree,可以:

git worktree list

會列出每個工作目錄的路徑、目前的 commit、checkout 到哪個分支。

幾個容易踩到的地方

同一個分支不能同時被兩個 worktree checkout。 如果 main 已經在別的 worktree 裡簽出了,你在另一個地方想再 checkout 一次 main,git 會直接拒絕,跳出 'main' is already checked out at '...'。要嘛開新分支,要嘛用 --detach 進入 detached HEAD 狀態(單純想看某個 commit、不打算在上面 commit 新東西的話,detached HEAD 完全夠用)。

node_modules、.env 不會跟著共用。 worktree 共用的只有 .git 裡的東西,工作目錄裡沒進版控的檔案,每個資料夾都要自己準備一份。如果專案的相依套件很肥、安裝要花好幾分鐘,這是使用 worktree 前要考慮的成本——不是每次臨時看一下 main 都划算開一個新 worktree,看情況決定。

手動刪資料夾不算清乾淨。 worktree 的中繼資訊實際上放在 .git/worktrees/ 底下,如果你直接把 order-refund-hotfix/ 資料夾丟到垃圾桶,而不是用 git worktree remove.git/worktrees/ 裡會留下一筆指向不存在路徑的孤兒紀錄。之後可以用 git worktree prune 清掉這些殘留。

IDE 建議當成獨立專案開。 兩個 worktree 資料夾內容差很多的時候(例如一個在改前端、一個在追 bug),同一個 IDE 視窗開兩個 worktree 容易搞混現在到底在哪個分支上,習慣上我會另開一個視窗,一個資料夾一個視窗對應一個分支。

SourceTree 沒有這個功能,只能靠終端機

前一篇提到 rebase 在 SourceTree 裡可以整套用滑鼠做完,worktree 不行——目前的 SourceTree 沒有管理 worktree 的圖形介面,新增、移除都得自己在終端機(或 SourceTree 內建的 Terminal 按鈕)裡打指令。

這不是 SourceTree 的缺點,worktree 本來就是相對冷門、CLI 導向的功能,日常用到的頻率遠不如切分支、commit、rebase。知道這件事的好處是:遇到問題不用在選單裡到處找,直接開終端機就對了。

請 AI 幫忙操作 worktree 時,指令要多一層小心

跟 rebase 不一樣的地方在於,worktree 的操作會實際在你的檔案系統上新增、刪除資料夾——不是重寫 git 歷史那種「錯了還能救回來」的風險,是「資料夾真的被砍掉」的風險。請 AI 助理代勞時,這個分寸要講清楚:

請幫我在目前 repo 旁邊新增一個 worktree 來看 main 上的 bug。

規則:
1. 用 git worktree add 建立,路徑固定在 ../order-refund-hotfix,
   不要用其他路徑,也不要覆蓋已存在的資料夾。
2. 建立前先執行 git worktree list,確認 main 目前沒有被其他
   worktree 簽出;如果已經被簽出,停下來跟我說,不要自動改用
   detached HEAD 或其他分支名稱。
3. 之後如果要移除,一律用 git worktree remove,不要用 rm -rf
   直接刪資料夾。移除前先確認該工作目錄沒有未 commit 的修改,
   有的話列出來讓我看,不要自動捨棄。

重點跟先前那篇一樣:把「路徑」「要不要覆蓋」「刪除方式」這幾個會動到實際檔案的地方寫死,AI 遇到模糊指令時,比較容易自己找一個看起來合理的路徑或做法補上,而資料夾這種東西,猜錯了不是 diff 一下就能看出來的。

worktree 跟 rebase,解決的是不同的摩擦

Rebase 處理的是「main 的紀錄乾不乾淨」;worktree 處理的是「手上這件事還沒做完,卻要同時處理另一件事」。兩個沒有誰取代誰的關係,但擺在一起用,日常開發會順很多:分支歷史靠 rebase 保持乾淨,臨時要看別的分支不用中斷手上的工作、也不用跟 stash 賭運氣。