レビュー作業やVibe Codingなど、1つのプロジェクトで複数のブランチを同時に触りたい場面があります。
このようなシーンでGit Worktreeを使うと、ブランチ(commitish)ごとに独立した作業ディレクトリを持てるため、効率的に作業を進められます。
git worktree add "作業ディレクトリ名" "ブランチ名(commitish)"しかし、Worktreeごとにローカルの検証環境を立ち上げると、URLやポート番号などのリソース競合が発生し、うまく動作しないことがあります。
そこで、本記事では、以下のコマンドを使って作業中のWorktreeの内容をMain worktreeに反映する方法を紹介します。これを使えば、Main worktreeで手動リビルドするなりホットリロードするなりして、リソース競合を避けつつローカルデプロイできます。
git \
-C "$(git worktree list --porcelain | head -n 1 | sed -e 's/^worktree //')" \
checkout --detach "$(git rev-parse HEAD)"Worktree利用時の検証環境の競合問題
Worktreeによる複数作業ブランチの作成は、実装やコードリーディングなど、ブランチ間で作業の独立性が担保されている間は非常に有用です。
一方でWeb開発などの検証には、URLやポート番号などのリソース競合を避ける必要があります。素朴な対応方法として、大きく2パターンありますが、どちらも一長一短です。
- 既存の検証環境を終了して、作業中のブランチに対応する環境を立ち上げる
- ブランチごとにリソースを分ける
- 異なるポート番号やURLを設定する
- 仮想環境を分ける
個人的に好ましくない点は以下。
- 1も2も認知負荷が発生
- 1はどの作業ブランチが検証環境を実行していて、いつ終了させるべきかを意識する負荷が発生
- 2は閲覧中の検証環境とブランチの対応を意識する負荷が発生
- 2はリソースの無駄使いになりやすい
- 挙動を比べたいケースでもない限り、同時に複数の検証環境を立ち上げる必要はない
ローカルデプロイの起点をMain worktreeに一本化
リソース競合を避けつつ、Worktreeの利便性を活かすには、ローカルデプロイの起点をMain worktreeに一本化する方法が有効です。
Main worktreeとは、git initやgit cloneで作成される、通常の作業ディレクトリのことを指します。
作業中のworktreeの内容をMain worktreeに反映して、反映結果をローカルデプロイすると、ローカルデプロイの起点をMain worktreeに一本化できます。リソース競合を避けられます。
反映方法はいくつかありますが、以下のgit checkout --detachを使う方法が一番破壊的影響が少なくておすすめです。あらかじめ pnpm run devやcargo watchなどのローカルデプロイコマンドをMain worktreeで実行しておき、ホットリロードが走る状態にしておくと、デプロイ結果がただちにビルドできて便利です。
git \
-C "$(git worktree list --porcelain | head -n 1 | sed -e 's/^worktree //')" \
checkout --detach "$(git rev-parse HEAD)"
# 必要に応じてローカルデプロイコマンドを実行
# Main worktree上でホットリロードが走る場合は不要ただし、Main worktreeのHEADがdetached状態になるため、Main worktreeでは開発作業をせず、ローカルデプロイ専用として使うのがおすすめです。
軽く解説しておくと、-Cオプションを指定すると、Git操作を現在の作業ディレクトリではなく、指定したディレクトリで実行できます。これにより、作業ブランチのWorktreeにいながら、Main worktreeに対してgit checkout --detachを実行できます。また、checkout先には、作業ブランチの最新コミット(git rev-parse HEAD)を指定しています。
Gitでは複数のworktreeで同じ作業ブランチをチェックアウトできないため、--detachオプションを付与して、ブランチではなくコミットをチェックアウトしています。
もし、コミット前の変更内容や、ignoredなファイルも反映したい場合は、以下のようにrsyncコマンドを使う方法もありそうです。
MAIN_WORKTREE_DIR="$(git worktree list --porcelain | head -n 1 | sed -e 's/^worktree //')"
git \
-C "$MAIN_WORKTREE_DIR" \
checkout --detach "$(git rev-parse HEAD)"
rsync -a --delete \
--exclude '.git/' \
"$PWD/" \
"$MAIN_WORKTREE_DIR/" \rsyncに--deleteオプションを指定すると、同期元に存在しないファイルが同期先から削除されます。完全同期を実現する一方、不意な削除リスクもあるため注意してください。
Atusy's blog