很多人第一次想做“多功能並行開發”時,直覺都是多開幾個分支,然後在一個儲存庫目錄裡來回 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__
  • *.db
  • snapshot_*
  • .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

然後再重新走合併流程。

只改了前端,要不要重啟後端

通常不需要。很多時候靜態資源重新建置並部署成功後,重新整理快取就夠了。