快轉到主要內容
  1. 教學文章/

Git worktree 實戰:同時開多個分支、平行測試與安全清理完全攻略

·11 分鐘· loading · loading · ·
Git Worktree Branch Workflow Version-Control
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
版本控制: Git - 本文屬於一個選集。
§ 11: 本文

featured

一. 前言:你真的不需要一直切 branch 切到頭暈
#

你正在 feature/login 寫一個登入流程。 檔案改到一半,測試還沒全綠。 突然線上出 bug,必須立刻切到 main 開 hotfix。 你看著工作目錄裡一堆未完成修改,心裡開始浮現三個選項。 第一個選項:硬切 branch,然後讓 Git 對你大喊「你有未提交修改」。 第二個選項:先 git stash,祈禱等一下記得拿回來。 第三個選項:亂 commit 一個 wip do not look,再發誓之後會整理。 拍拍君知道,第三個選項聽起來很熟。 但 Git 其實還有一個更乾淨的工具:git worktreeworktree 讓同一個 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 名稱。 例如 starshipoh-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 installpnpm install。 Rust 專案通常可以透過共用 target directory 節省空間,但要看團隊設定。 第五個陷阱:忘記清理舊 worktree。 每週或每兩週跑一次:

git worktree list

看到已 merge、已 review、已不需要的目錄,就清掉。 工具幫你平行工作,不代表工具會幫你整理房間。 這部分還是人類和拍拍君一起負責。

十三. 拍拍君推薦的命名方式
#

Worktree 的目錄名稱最好讓人一眼看懂用途。 不要叫 project2project-newproject-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 的日子。

延伸閱讀
#

版本控制: Git - 本文屬於一個選集。
§ 11: 本文

相關文章

Git stash 實戰:暫存工作現場、切換任務與 patch 管理完全攻略
·11 分鐘· loading · loading
Git Git-Stash Patch Workflow Version-Control
Git Branch 策略完全攻略:feature branch、main、hotfix 與協作流程
·11 分鐘· loading · loading
Git Branch Workflow Feature-Branch Hotfix
Git bisect 實戰:快速定位壞 commit 與除錯流程完全攻略
·9 分鐘· loading · loading
Git Bisect Debugging Regression Version-Control
Git cherry-pick 實戰:精準搬運 commit、修補 hotfix 與分支同步
·11 分鐘· loading · loading
Git Cherry-Pick Hotfix Commit Version-Control
Git rebase 完全攻略:整理 commit 歷史、互動式 rebase 與衝突處理
·11 分鐘· loading · loading
Git Rebase Interactive-Rebase Commit-History Version-Control
GitHub Actions 進階:Matrix Build + 自動發佈 PyPI
·5 分鐘· loading · loading
Github-Actions Cicd Pypi Matrix-Build Python