複数機能を並行開発しようとすると、最初は「ブランチをいくつか作って、一つのリポジトリで checkout を切り替えればいい」と考えがちです。

短時間ならそれでも動きます。ただ、片方で新機能を作りながら別のバグを直し、さらにローカルサービスまで起動し始めると、どのコードを実行しているのか分からなくなりやすいです。

私は最終的に、メインのリポジトリは安定ブランチ用に残し、実際の開発は git worktree で機能ごとに独立ディレクトリを作る形に固定しました。

これなら一台のマシンで複数ブランチを同時に扱っても、ディレクトリ、プロセス、ビルド成果物が混ざりにくくなります。

なぜブランチ切り替えではなく Worktree にしたのか

難しいのは「新しいブランチを作ること」ではありません。

本当に困るのは、次のようなズレです。

  • 新機能を確認しているつもりでも、サービスは古いディレクトリから起動している
  • 別ブランチのビルド成果物が残っている
  • git add . で関係ない生成物までコミットする
  • 本番障害対応と新機能開発が同じ作業ツリーで混ざる

git worktree は、ブランチごとに別の作業ディレクトリを持てます。

私にとって一番大きい利点は、ブランチ数ではなく作業環境そのものを分離できることです。

私が固定しているディレクトリ規約

長く使うなら、最初にルールを決めた方が楽です。

私の例では次のようにしています。

  • メインリポジトリ:/Users/kuangyongjie/charting_library
  • worktree のルート:/Users/kuangyongjie/worktrees
  • ブランチ名:codex/<feature-name>
  • メインブランチ:master

機能開発は例えば次の場所に置きます。

/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 内だけで行う

このルールはかなり重要です。

例えばフロントエンドがサブディレクトリにあるなら、必ず対象 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 が次のような worktree パスを指していることを確認します。

/Users/kuangyongjie/worktrees/<feature-name>/...

ここが違っていれば、そもそも対象ブランチをテストしていません。

コミット時に git add . を避ける理由

並行開発では、私は意図的に保守的にコミットします。

worktree の中にキャッシュやローカル DB、スナップショット、OS の生成ファイルが残っていることがあるためです。

例えば次のようなものです。

  • __pycache__
  • *.db
  • snapshot_*
  • .DS_Store

そのため、基本は対象ファイルだけを追加します。

cd "$WT"
git add <file1> <file2> <file3>
git status --short
git commit -m "feat/fix: <summary>"

git status --short を一度見るだけでも、余計なファイルを混ぜる事故をかなり減らせます。

マージはメインディレクトリだけで行う

機能が完成した後のマージは worktree 側ではなく、メインリポジトリ側で行います。

cd /Users/kuangyongjie/charting_library
git branch --show-current   # master であることを確認

# 必要なら先に更新
# git pull --rebase central master

git merge --no-ff codex/<feature-name>

重要なのは --no-ff 自体より、今いるディレクトリとブランチを確認してからマージすることです。

もし次のエラーが出たら、

MERGE_HEAD exists

前回のマージが終わっていない可能性があります。

git merge --abort

で一度戻してから、改めて更新とマージを行います。

リモートへ push する場合は次のようにします。

cd /Users/kuangyongjie/charting_library
git push <remote> master

環境によってリモート名は origin とは限りません。私の環境では central を使うケースもあります。

Fast-forward を使いたい場合

「機能ブランチを独立起動して確認し、問題なければ merge commit を作らず統合したい」なら 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

別ターミナルから PID と cwd を確認します。

lsof -nP -iTCP:9544 -sTCP:LISTEN
ps -p <PID> -o pid,ppid,command
lsof -a -p <PID> -d cwd -Fn

「分岐ブランチでテストした」と思っていても、実際には別ディレクトリのプロセスを見ていた、という失敗はかなり起こりやすいです。

私が固定しているルール

長期運用するなら、最低限これだけは固定しておくと安定します。

  • 1 機能につき 1 worktree
  • 1 タスクや 1 会話を 1 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 します。

MERGE_HEAD exists

前回のマージが途中です。

git merge --abort

で中断してから、改めて進めます。

フロントエンドだけ変更した場合、バックエンド再起動は必要か

通常は不要です。静的アセットの再ビルドと配置が済んでいれば、キャッシュを更新するだけで反映されることも多いです。