Git Worktree 多分支並行開發:一套不互相汙染的標準流程
很多人第一次想做“多功能並行開發”時,直覺都是多開幾個分支,然後在一個儲存庫目錄裡來回 checkout。短時間看起來還能用,但只要你一邊改功能、一邊跑服務、一邊修線上問題,很快就會發現這套方式特別容易串。
我後來基本固定成了另一種做法:主目錄只保留穩定分支,真正開發時用 git worktree 給每個功能開一個獨立目錄。這樣你在同一台機器上可以同時維護多個功能分支,而且目錄、程序、建置產物基本都隔開了。
如果你現在在搜 Git 多分支並行開發、Git worktree 用法、Worktree 標準流程,這篇文章就是圍繞這些問題寫的。
為什麼我最後選了 Git Worktree,而不是反覆切分支
真正麻煩的從來不是“怎麼建立一個新分支”,而是你怎麼確保自己測試的就是這個分支、服務跑的也是這個目錄、最後提交時不會把別的功能殘留一起帶進去。
在一個儲存庫目錄裡反覆切分支,最容易出現幾類問題:
- 你以為自己在測新功能,結果服務還是從舊目錄啟動的
- 某個功能留下來的建置產物,汙染了另一個分支
git add .一把梭,把根本不該提交的檔案也帶上去了- 一邊修線上 bug,一邊改新功能,目錄狀態越來越亂
git worktree 的好處就在這裡。它不是讓你“更快建立分支”而已,而是讓每個功能擁有自己單獨的工作目錄。對我來說,這個隔離感比什麼都重要。
我自己固定下來的目錄約定
為了避免後面越用越亂,我會先把幾個約定定死:
- 主儲存庫目錄:
/Users/kuangyongjie/charting_library - worktree 根目錄:
/Users/kuangyongjie/worktrees - 分支命名:
codex/<feature-name> - 主分支:
master
也就是說,主目錄只負責保持穩定和合並。真正開發一個功能時,我會把它放到一個新的 worktree 目錄裡,比如:
/Users/kuangyongjie/worktrees/hotspot-ff
這個約定看起來很小,但實際很有用。因為你只要看到路徑,就知道當前到底在主儲存庫,還是在某個功能分支目錄。
標準做法其實就幾步,但每一步都別省
先準備一下 worktree 根目錄:
mkdir -p /Users/kuangyongjie/worktrees
然後從主儲存庫建立一個獨立 worktree 和新分支:
REPO=/Users/kuangyongjie/charting_library
WT=/Users/kuangyongjie/worktrees/<feature-name>
BRANCH=codex/<feature-name>
BASE=master
# 如果远端可用,先更新 master
# git -C "$REPO" pull --rebase central "$BASE"
git -C "$REPO" worktree add "$WT" -b "$BRANCH" "$BASE"
cd "$WT"
git branch --show-current
正常情況下,最後應該看到:
codex/<feature-name>
這一步很關鍵,因為它明確告訴你:從現在開始,這個功能的程式碼、建置、測試,都應該只發生在 "$WT" 裡。
只在分支目錄裡開發和測試,這條規則最好別破
後面無論你改前端、跑後端,還是做本地驗證,都只在這個 worktree 目錄裡做。
比如前端專案在子目錄裡,你就進這個分支目錄再建置:
cd "$WT/trendradar-service/trendradar-web"
npm run build
# 生产混淆构建
# npm run build:multi-layer
我自己踩過一個很常見的坑,就是程式碼明明改了,頁面卻還是舊效果。後來一查,不是程式碼沒生效,而是服務壓根跑在另一個目錄裡。
所以只要出現“我明明改了但沒變化”,我第一反應不是懷疑框架,而是先查當前程序的 cwd。
lsof -nP -iTCP:<port> | sed -n '1,5p'
ps -p <pid> -o pid,ppid,command
lsof -p <pid> | rg "cwd"
你最後必須確認 cwd 指向的是:
/Users/kuangyongjie/worktrees/<feature-name>/...
不然你測試的東西很可能根本不是這個分支。
提交的時候,我基本不用 git add .
這一點我會故意保守一點。
只要是 worktree 並行開發,我提交前幾乎都會只加目標檔案,而不是直接 git add .。因為一旦你本地有執行產物、快取檔案、快照檔案,這一把很容易把髒東西也帶進去。
像這些東西就很常見:
__pycache__*.dbsnapshot_*.DS_Store
我更傾向於這樣提:
cd "$WT"
git add <file1> <file2> <file3>
git status --short
git commit -m "feat/fix: <summary>"
先看一眼 git status --short 再 commit,這個動作雖然小,但能擋掉很多低階錯誤。
合併回主分支時,只在主目錄做
功能做完以後,合併動作我只會在主儲存庫目錄執行,不會在 worktree 裡順手 merge。
cd /Users/kuangyongjie/charting_library
git branch --show-current # 必须是 master
# 如远端可用,先更新
# git pull --rebase central master
git merge --no-ff codex/<feature-name>
這裡的重點其實不是 merge --no-ff 本身,而是你要確認自己真的站在主目錄、主分支上。
如果你遇到:
MERGE_HEAD exists
通常說明上次 merge 沒收乾淨,可以先:
git merge --abort
然後再重新執行 pull 和 merge。
如果你後面還要推遠端,再補一步:
cd /Users/kuangyongjie/charting_library
git push <remote> master
順手提醒一下,你這臺機器上常見遠端名是 central,不一定是 `origin“。這類問題通常是環境差異,不是 Git Worktree 本身的問題。
什麼情況下我會用 Fast-forward,而不是強行留 merge 記錄
如果你的要求是“分支單獨啟動測試,透過後再合併,而且不生成 merge 記錄”,那就可以走 fast-forward 的思路。
比如:
cd /Users/kuangyongjie/charting_library
git checkout master
git pull --rebase central master
git worktree add /Users/kuangyongjie/worktrees/hotspot-ff -b codex/hotspot-ff master
然後只在這個分支 worktree 裡啟動服務,比如:
cd /Users/kuangyongjie/worktrees/hotspot-ff/trendradar-service
./venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 9544
再開一個終端,確認當前程序的工作目錄確實就是這個分支:
lsof -nP -iTCP:9544 -sTCP:LISTEN
ps -p <PID> -o pid,ppid,command
lsof -a -p <PID> -d cwd -Fn
這個檢查動作非常值,因為很多“分支驗證透過”的結論,最後其實是建立在測錯目錄上的。
我現在基本固定下來的幾條規則
如果你也準備長期用 Worktree,我建議至少把下面幾條變成硬規則:
- 一個功能一個 worktree 目錄
- 一個對話或一個任務只對應一個 worktree
- 開工先跑
git branch --show-current - 提交前先看
git status --short - 合併只在主目錄
master執行
這幾條聽起來很普通,但真正能讓並行開發不亂掉,靠的就是這些基礎動作。
常見問題
本地頁面還是舊效果
先別急著懷疑程式碼。
先查服務程序的 cwd 是不是在目標 worktree,再確認你是不是已經在那個分支目錄裡重新建置過前端產物。這個問題特別常見。
fatal: 'origin' does not appear to be a git repository
一般不是 Git 掛了,而是遠端名寫錯了。先跑:
git remote -v
然後按實際遠端名 push。你這邊常見的是 central。
MERGE_HEAD exists
說明上次 merge 沒結束。先:
git merge --abort
然後再重新走合併流程。
只改了前端,要不要重啟後端
通常不需要。很多時候靜態資源重新建置並部署成功後,重新整理快取就夠了。