一. 前言:你真的不需要一直切 branch 切到頭暈 #
你正在 feature/login 寫一個登入流程。 檔案改到一半,測試還沒全綠。 突然線上出 bug,必須立刻切到 main 開 hotfix。 你看著工作目錄裡一堆未完成修改,心裡開始浮現三個選項。 第一個選項:硬切 branch,然後讓 Git 對你大喊「你有未提交修改」。 第二個選項:先 git stash,祈禱等一下記得拿回來。 第三個選項:亂 commit 一個 wip do not look,再發誓之後會整理。 拍拍君知道,第三個選項聽起來很熟。
但 Git 其實還有一個更乾淨的工具:git worktree。 worktree 讓同一個 repository 可以同時有多個工作目錄。 每個工作目錄可以 checkout 不同 branch。 你可以一邊保留原本未完成的功能,一邊在另一個資料夾修 hotfix。 不用 stash,不用反覆 checkout,不用把腦袋切成三份。 如果前面看過 Git Branch 策略完全攻略、Git stash 實戰 和 Git cherry-pick 實戰,那 worktree 就是另一塊很實用的拼圖。 Branch 負責分工作線。 Stash 負責短暫收起未完成現場。
Cherry-pick 負責精準搬 commit。 而 worktree 負責讓多條工作線「真的同時存在」在你的磁碟上。 今天這篇拍拍君會從基本概念開始,帶你建立、查看、移除 worktree。 接著會看幾個真實工作流:hotfix、PR review、版本測試、長期實驗分支。 最後再整理常見陷阱與清理方法。 先講結論:git worktree 不是每天都要用,但當你同時處理兩三件事時,它會讓你少很多精神負擔。
二. 安裝與基本概念:worktree 是 Git 內建功能 #
git worktree 是 Git 內建指令。 只要你的 Git 不太古老,通常都可以直接使用。 先確認版本:
git --version
如果尚未安裝 Git,可以用常見方式安裝:
# macOS
brew install git
# Ubuntu / Debian
sudo apt update
sudo apt install git
# Windows
winget install --id Git.Git -e
那 worktree 到底是什麼? 平常你 clone 一個 repository 後,會得到一個工作目錄。 裡面有檔案,也有一個 .git 目錄。 你在這個工作目錄裡 checkout 不同 branch。 同一時間,這個工作目錄通常只能在一個 branch 上。 git worktree 則允許你從同一份 Git database 掛出額外的工作目錄。 這些額外目錄共享同一份 commit、object、remote 資訊。 但每個目錄有自己的 working tree、index 和 HEAD。
換句話說,你不需要重新 clone 整個 repository。 你只是讓同一個 repo 多長出幾個「可工作的資料夾」。 可以把它想成這樣:
blog-tech/
main repository, currently on feature/login
../blog-tech-hotfix/
extra worktree, currently on hotfix/fix-token
../blog-tech-review-123/
extra worktree, currently on review/pr-123
這三個資料夾看起來像三份專案。 但它們其實共享同一個 Git 物件資料庫。 所以建立速度通常很快,也不會像重新 clone 那樣浪費空間。 這就是 worktree 最迷人的地方。 它給你多個工作目錄,但不要求你複製整個世界。
三. 第一個 worktree:建立另一個乾淨工作區 #
假設你現在在專案根目錄:
cd ~/projects/pypy-app
目前 branch 是 feature/login:
git status
輸出可能像這樣:
On branch feature/login
Changes not staged for commit:
modified: app/auth.py
你正在開發登入功能,而且還不想 commit。 這時候突然需要從 main 開一條 hotfix branch。 可以這樣做:
git worktree add ../pypy-app-hotfix -b hotfix/fix-token main
這行指令做了三件事。 第一,建立一個新目錄 ../pypy-app-hotfix。 第二,從 main 建立新 branch hotfix/fix-token。 第三,讓這個新 worktree checkout 到 hotfix/fix-token。 接著你可以進去:
cd ../pypy-app-hotfix
git status
你會看到乾淨的 hotfix 工作區。 原本 feature/login 裡那些未完成修改還留在原本資料夾。 兩邊互不干擾。 這比 stash 更直覺。 因為你不是把現場收進抽屜,而是直接開了另一張桌子。 修好 hotfix 後照常 commit:
git add app/auth.py tests/test_auth.py
git commit -m "Fix token refresh bug"
git push -u origin hotfix/fix-token
然後發 PR、等待 CI、請同事 review。 原本功能分支完全沒有被碰到。 回到原目錄時,它還是原本那個做到一半的狀態。
cd ../pypy-app
git status
這種分離感很舒服。 拍拍君私心覺得,這是 worktree 最適合入門的使用場景。
四. 查看 worktree:先知道自己開了幾張桌子 #
建立多個 worktree 之後,最重要的是不要忘記它們存在。 Git 提供了查看清單的指令:
git worktree list
你可能會看到:
/Users/pypy/projects/pypy-app abc1234 [feature/login]
/Users/pypy/projects/pypy-app-hotfix def5678 [hotfix/fix-token]
/Users/pypy/projects/pypy-app-review 9ab012c [review/pr-123]
每一行包含三個資訊。 第一個是工作目錄路徑。 第二個是目前 HEAD commit。 第三個是 checkout 的 branch。 如果想看更詳細資訊,可以加上 --porcelain:
git worktree list --porcelain
輸出會比較適合腳本解析:
worktree /Users/pypy/projects/pypy-app
HEAD abc1234...
branch refs/heads/feature/login
worktree /Users/pypy/projects/pypy-app-hotfix
HEAD def5678...
branch refs/heads/hotfix/fix-token
日常使用時,git worktree list 就很夠了。 拍拍君建議:只要你開始用 worktree,就養成常看清單的習慣。 不然幾週後你會在硬碟某個角落發現一個古老的 project-test-2-final-real。 然後完全想不起來它是誰。
五. 移除 worktree:清理要用 Git,不要只靠手動刪資料夾 #
工作完成之後,應該移除不再需要的 worktree。 假設 hotfix 已經 merge,可以這樣做:
git worktree remove ../pypy-app-hotfix
如果該 worktree 裡還有未提交修改,Git 會阻止你移除。 這是好事。 因為它避免你不小心刪掉工作成果。 如果你確定不要那些修改,可以加上 --force:
git worktree remove --force ../pypy-app-hotfix
但拍拍君要很認真地說:使用 --force 前請先跑一次 git status。 真的確定沒有要留的東西再刪。 如果你曾經直接用 Finder、檔案總管或 rm -rf 刪掉 worktree 目錄,Git 可能還記得那個 worktree。 這時候可以清理紀錄:
git worktree prune
prune 會移除那些已經不存在的 worktree metadata。 它不是拿來刪除正常 worktree 的第一選項。 正常流程還是先用 git worktree remove。 一個安全清理流程通常長這樣:
git worktree list
cd ../pypy-app-hotfix
git status
cd ../pypy-app
git worktree remove ../pypy-app-hotfix
git worktree list
看起來多幾步,但它能避免很多悲劇。 Git 已經夠容易讓人緊張了,我們不要自己增加恐怖故事。
六. Worktree 與 branch 的關係:一個 branch 通常不能同時 checkout 兩次 #
新手使用 worktree 時,最常遇到的錯誤是這個:
fatal: 'feature/login' is already checked out at '/Users/pypy/projects/pypy-app'
意思是:這個 branch 已經被某個 worktree checkout 了。 Git 預設不讓同一個 branch 同時在兩個 worktree 被 checkout。 這個限制很合理。 如果兩個資料夾同時在同一個 branch 上,各自 commit、rebase、reset,Git 很容易變得混亂。 所以 worktree 的常見做法是:每個 worktree 對應不同 branch。 例如:
git worktree add ../pypy-app-docs -b docs/update-readme main
或 checkout 已存在但目前沒被其他 worktree 使用的 branch:
git worktree add ../pypy-app-benchmark benchmark/new-cache
如果你只是想看某個 commit,而不是在 branch 上工作,也可以建立 detached HEAD worktree:
git worktree add --detach ../pypy-app-old v1.2.0
這會讓新目錄 checkout 到 v1.2.0,但不在任何 branch 上。 很適合做歷史版本測試。 不過 detached HEAD 不適合直接長期開發。 如果測到一半發現需要修改,請先建立 branch:
git switch -c debug/v1.2-token
這樣你的 commit 才不會飄在空中。
七. 實戰一:hotfix 不打斷現在的 feature 開發 #
這是最典型的 worktree 場景。 你在 feature/payment-ui 做到一半,突然要修正式環境問題。 不要急著 stash。 先在原本 repo 執行:
git worktree add ../pypy-app-hotfix -b hotfix/payment-timeout origin/main
接著進入 hotfix 目錄:
cd ../pypy-app-hotfix
確認狀態:
git status
開始修 bug、補測試、commit:
git add .
git commit -m "Fix payment timeout handling"
git push -u origin hotfix/payment-timeout
hotfix merge 後,可以回到原本 feature worktree,把 main 的修正帶回來。 如果團隊使用 merge:
cd ../pypy-app
git fetch origin
git merge origin/main
如果團隊偏好 rebase:
git fetch origin
git rebase origin/main
然後清掉 hotfix worktree:
git worktree remove ../pypy-app-hotfix
這個流程的重點是:你的 feature 現場完全沒有被迫暫停。 未提交修改仍然留在原地。 你只是開了另一個乾淨空間救火。 對需要頻繁支援 production 的工程師來說,這真的很實用。
八. 實戰二:快速 review PR,不弄髒自己的主工作區 #
另一個好用場景是 review 同事的 PR。 假設你要 review origin/feature/report-export。 你可以建立一個專門的 review worktree:
git fetch origin
git worktree add ../pypy-app-review-report origin/feature/report-export
注意這裡沒有 -b。 如果你直接 checkout remote branch,通常會得到 detached HEAD。 如果只是讀 code、跑測試、試功能,這樣可以接受。
cd ../pypy-app-review-report
pytest
python -m pypy_app.demo_report
如果你想在 review 時順手推一些修改,建議建立本地 branch:
git switch -c review/report-export-fixes
做完 review 後刪掉目錄:
cd ../pypy-app
git worktree remove ../pypy-app-review-report
這比在主工作區不斷切 PR branch 乾淨很多。 尤其是大型前端或 ML 專案,切 branch 後常常需要重新安裝依賴、重建 cache。 用 worktree 分開放,心理上也比較清楚:這個資料夾就是拿來 review 的。 Review 完就丟掉。 不要讓 PR 留在你的主工作區當地縛靈。
九. 實戰三:同時比較兩個版本的行為 #
有時候你不是要開發,而是要比較版本。 例如:v1.4.0 的 CLI 很快,但 main 變慢了。 你可以建立兩個 worktree:
git worktree add --detach ../pypy-app-v140 v1.4.0
git worktree add ../pypy-app-main main
接著分別跑 benchmark:
cd ../pypy-app-v140
python -m pytest benchmarks/test_cli.py
cd ../pypy-app-main
python -m pytest benchmarks/test_cli.py
兩邊的檔案狀態完全分開。 你不用一直 git checkout v1.4.0、跑測試、再 git checkout main。 如果專案會產生 build artifacts,也不會一直互相污染。 這對 Rust、Node、Python native extension 專案尤其有感。 因為不同 branch 可能有不同依賴或不同編譯產物。 同一個工作目錄來回切,常常會讓 cache 進入一種「我不知道我是誰」的狀態。 Worktree 雖然多佔一些檔案空間,但換來的是可預測性。 除錯時,可預測性非常珍貴。
十. 實戰四:長期實驗分支不要干擾日常開發 #
有些 branch 不是一天兩天會完成。 例如:重構設定系統、替換 UI framework、改資料庫 schema。 這類實驗通常會存在一段時間。 如果它一直佔著你的主工作區,你每天都會被迫在「日常小修」與「大型實驗」之間切換。 這很容易造成混亂。 可以把大型實驗放到獨立 worktree:
git worktree add ../pypy-app-config-lab -b lab/config-loader main
日常開發留在主目錄。 實驗分支留在 ../pypy-app-config-lab。 當你有時間研究,就進去那個目錄。 沒有時間,就不要碰它。 這種分離對注意力很友善。 而且你可以為不同 worktree 設定不同 editor workspace。 例如 VS Code 可以分別開:
code ~/projects/pypy-app
code ~/projects/pypy-app-config-lab
每個視窗都有自己的 git 狀態。 你不會在一個 editor 裡看到十幾個跨任務的修改。 拍拍君覺得這點很重要。 工具不只影響版本控制,也影響你腦袋裡的任務邊界。
十一. Worktree 與 stash:不是誰取代誰,而是用途不同 #
看到這裡你可能會問:那是不是以後都不用 git stash? 不是。 stash 還是很好用。 只是它適合短暫、輕量的現場保存。 例如:你要拉最新 main,先暫存一個小修改。 或你要測一下別人的 branch,五分鐘後就回來。 worktree 則適合比較明確的平行任務。 例如 hotfix、PR review、版本比較、長期實驗。
可以這樣判斷:
- 只是暫時收起目前修改,幾分鐘後回來:用
stash。 - 需要一個乾淨工作區處理另一件事:用
worktree。 - 需要同時保留多個 branch 的檔案狀態:用
worktree。 - 只是想整理 commit 歷史:用
rebase,不是 worktree。 工具本身沒有高低。 重點是選對場景。 如果你每次切任務都靠 stash,一週後git stash list可能會變成考古現場。 如果你每個小修改都開 worktree,硬碟也會慢慢變成資料夾森林。 剛剛好最好。
十二. 常見陷阱:worktree 很好,但不是完全沒有邊角 #
第一個陷阱:忘記自己在哪個目錄。 多個 worktree 長得很像,尤其是同一個專案。 請善用 shell prompt 顯示 branch 名稱。 例如 starship、oh-my-zsh 或 Git prompt 都可以幫忙。 第二個陷阱:刪資料夾不清 metadata。 如果手動刪掉 worktree,記得回主 repo 跑:
git worktree prune
第三個陷阱:同一個 branch 不能到處 checkout。 看到 already checked out 錯誤時,不要慌。 先跑:
git worktree list
找出是哪個目錄正在使用那條 branch。 第四個陷阱:忽略依賴與環境檔。 不同 worktree 共享 Git object,但不共享虛擬環境。 Python 專案常見做法是在每個 worktree 裡各自建立 .venv。
python -m venv .venv
source .venv/bin/activate
pip install -e .[dev]
如果使用 uv,可以讓它自己管理:
uv sync
Node 專案可能每個 worktree 都要 npm install 或 pnpm install。 Rust 專案通常可以透過共用 target directory 節省空間,但要看團隊設定。 第五個陷阱:忘記清理舊 worktree。 每週或每兩週跑一次:
git worktree list
看到已 merge、已 review、已不需要的目錄,就清掉。 工具幫你平行工作,不代表工具會幫你整理房間。 這部分還是人類和拍拍君一起負責。
十三. 拍拍君推薦的命名方式 #
Worktree 的目錄名稱最好讓人一眼看懂用途。 不要叫 project2、project-new、project-final。 那是混亂的開始。 比較好的命名方式:
pypy-app-hotfix-token
pypy-app-review-123
pypy-app-lab-config
pypy-app-v140
pypy-app-benchmark-cache
也可以用固定前綴把 worktree 放在同一層:
~/projects/pypy-app
~/projects/pypy-app.worktree.hotfix-token
~/projects/pypy-app.worktree.review-123
~/projects/pypy-app.worktree.lab-config
這樣檔案總管或 ls 看起來會很整齊。 如果你常常開 worktree,也可以寫小 alias:
alias gwl='git worktree list'
alias gwr='git worktree remove'
alias gwp='git worktree prune'
或用 shell function 包裝常見流程:
gw-hotfix() {
name="$1"
git worktree add "../$(basename "$PWD")-hotfix-$name" -b "hotfix/$name" origin/main
}
使用方式:
gw-hotfix token-refresh
這會建立:
../pypy-app-hotfix-token-refresh
以及 branch:
hotfix/token-refresh
小工具不一定要複雜。 只要能讓你少打一點容易打錯的指令,就很值得。
十四. 一套實用的 worktree 工作流 #
最後把整套流程串起來。 假設你正在功能分支上工作:
cd ~/projects/pypy-app
git status
突然需要 review PR 123:
git fetch origin
git worktree add ../pypy-app-review-123 origin/pr/123
cd ../pypy-app-review-123
uv sync
pytest
Review 完之後:
cd ../pypy-app
git worktree remove ../pypy-app-review-123
接著又有 hotfix:
git fetch origin
git worktree add ../pypy-app-hotfix-token -b hotfix/token origin/main
cd ../pypy-app-hotfix-token
uv sync
修好後:
git add .
git commit -m "Fix token refresh"
git push -u origin hotfix/token
PR merge 之後清理:
cd ../pypy-app
git worktree remove ../pypy-app-hotfix-token
git fetch origin
git rebase origin/main
最後檢查:
git worktree list
整個過程裡,你原本的 feature branch 沒有被迫 stash。 你的 review、hotfix、日常開發各自有清楚邊界。 這就是 worktree 的價值。 它不是讓 Git 變得更花俏。 它是讓你的工作空間更像真實世界的任務分工。
十五. 結語:平行工作不是混亂的理由 #
現代開發很少是單線任務。 你可能早上寫 feature,中午 review PR,下午修 hotfix,晚上還要比較兩個版本的效能。 如果所有事情都塞在同一個工作目錄裡,Git 再強也救不了你的注意力。 git worktree 給你的不是新魔法,而是一種更乾淨的物理分隔。 每個任務一個工作區。 每個工作區一個 branch 或一個版本。 任務結束就清掉。 很樸素,但很有效。
拍拍君建議你下次遇到「手上功能還沒做完,但突然要修另一件事」時,不要立刻 stash。 先試試:
git worktree add ../project-hotfix -b hotfix/something origin/main
你可能會發現,原來 Git 可以不用每天都像在玩倉庫搬家。 把任務分開,腦袋也會輕一點。 乾淨工作區,快樂開發。 拍拍君保證,這個習慣養起來之後,你會很難回到到處 stash 的日子。