💭

生成AI時代に役立つ!Git Worktree使ってますか?

に公開

はじめに

こちらはe-dash advent calendar 2025の6日目の記事です。

生成AIを活用した開発が当たり前になった今、みなさんはどのように複数の作業を並行して進めていますか?

レビュー待ちのPRがある中で緊急のバグ修正が入ったり、ClaudeやCursorといったAI Agentが別のブランチで作業している間に自分でも別の機能を実装したり。

こうした場面で git stash や頻繁な git checkout を繰り返すのは、開発フローを阻害する大きな要因になります。

本記事では、こうした課題を解決する Git Worktree と、その運用を改善する gwq というツールを紹介します。さらに実運用で直面した課題と、その解決策としてsymlinkを活用した自動化の仕組みについても解説します。

git worktreeとは

基本的な概念

通常、1つの作業ディレクトリでは1つのブランチしかcheckoutできません。別のブランチで作業したい場合は、現在の変更を git stash で退避させるか、コミットしてから git checkout でブランチを切り替える必要があります。

しかしGit Worktreeを使うと、同じリポジトリの異なるブランチを、それぞれ独立したディレクトリで同時に扱えるようになります。例えば以下のような構成が可能です。

~/projects/myapp/              # メインの作業ディレクトリ (mainブランチ)
~/projects/myapp-feature-a/    # feature-aブランチ専用
~/projects/myapp-hotfix/       # hotfixブランチ専用

これにより複数の独立した作業を同時進行でき、余計なcheckout/stashも不要になります。

基本的な使い方

以下のコマンドで新しいWorktreeを作成できます。

# 新しい Worktree を作成
git worktree add ../project-feature-a feature-a
# 移動してエディタを立ち上げる
cd ../project-feature-a && <some-editor>

Git Worktreeの詳細は以下の記事がより分かりやすいです。

https://zenn.dev/hiraoku/articles/56f4f9ffc6d186

Note

AnthropicでもClaude Codeを使う際にgit worktreeの使用を推奨しています。

https://www.anthropic.com/engineering/claude-code-best-practices

gwqで快適なworktree運用を実現する

git worktreeの課題

git worktreeは便利な機能ですが、実運用では以下のような課題があります。

コマンド体系の違い

git worktree addgit worktree listgit worktree remove など、通常のgitコマンドとは異なる体系のため、慣れるまで時間がかかります。

作成場所の散在
worktreeの作成場所を毎回指定する必要があり、管理が煩雑になりがちです。

削除の手間
削除時には手動でパス指定が必要で、worktreeが増えてくるとクリーンアップ作業が面倒になります。

gwqとは

これらの課題を解決するのがgwqというライブラリです。

https://github.com/d-kuro/gwq

gwqについての解説は以下の記事の後半が詳しいです。

https://zenn.dev/d_kuro/articles/6321fa66b9180f

gwqの使い方

gwqでWorktreeを作成すると、以下のように操作できます。

# 新しいブランチを作成してWorktreeを作成
gwq add -b feat/new-func

# 作成されるパス例:
# ~/worktrees/github.com/<organization-name>/<repository-name>/feat-new-func

# 既存ブランチからWorktree作成も可能
gwq add some-existing-branch

# インタラクティブにブランチを選択してworktree作成
gwq add -i

# Fuzzy Finderでworktreeを選択して削除
gwq remove

# worktree一覧を表示
gwq list

gwqの3つの利点

1. gitライクな操作感
git add -bのように、慣れ親しんだgitと同様の感覚で操作できます。学習コストが低く、すぐに使いこなせます。

2. 一貫した命名規則と集約管理
base directory配下に、組織名/リポジトリ名/ブランチ名 という一貫した命名規則でworktreeが作成されます。これにより、どのプロジェクトのどのブランチなのかが一目瞭然で、管理が容易になります。

3. Fuzzy Finderによる快適な操作
作成や削除の際に、Fuzzy Finderでインタラクティブに選択できます。タイプ量が減り、ミスも防げます。

実運用で直面した課題

ここまでgit worktreeとgwqの紹介をしてきましたが、実際に運用してみるといくつかの課題に直面しました。特に環境変数など、Git管理外のファイルの扱いが問題になります。

課題: 環境変数ファイルがコピーされない

.gitignoreで除外している環境変数ファイル(.env.env.localなど)はworktree先にコピーされないため、毎回手動でコピーしなければなりません。

開発環境によっては複数の環境変数ファイルが必要になることもあり、worktreeを作成するたびにこれらをコピーする作業は非効率的です。

symlinkによる自動化で課題を解決

解決のアイデア

「worktreeを作成したときに自動的にsymlinkを張れば良いのではないか」と考え、実装してみました。

symlinkを使えば、メインリポジトリのファイルを各worktreeから参照できるようになります。これにより環境変数ファイルを一元管理しつつ、全てのworktreeで利用可能になります。

実装の仕組み

この仕組みは2つのコンポーネントで構成されます。

  1. Gitのpost-checkoutフック: worktree作成時に自動的にsymlinkを生成
  2. gwqラッパー関数: gwq add 実行時にフックを起動

Gitのpost-checkoutフックをグローバルテンプレートに設定し、worktree作成後にメインリポジトリへのsymlinkを自動生成します。

このスクリプトは以下の処理を行います:

  1. worktree内での実行かどうかを判定
  2. メインリポジトリのパスを特定
  3. 指定したファイルパターン(.env*)をメインリポジトリ内から検索
  4. 見つかったファイルへのsymlinkをworktree内に作成
post-checkoutフックの実装(クリックで展開)
#!/bin/sh
# ~/.git-templates/hooks/post-checkout

prev_head=$1
new_head=$2
branch_checkout=$3

# worktree判定: commondirファイルの存在でworktreeかどうかを判別
# - 通常のリポジトリ: .git/commondir は存在しない
# - worktree: .git/commondir が存在し、メインリポジトリの.gitディレクトリを参照
if git rev-parse --is-inside-work-tree >/dev/null 2>&1 &&
   [ -f "$(git rev-parse --git-dir)/commondir" ]; then
    is_worktree="true"
else
    is_worktree="false"
fi

# ブランチチェックアウト時 かつ worktree内でのみ実行
if [ "$branch_checkout" = "1" ] && [ "$is_worktree" = "true" ]; then
    # Gitディレクトリのパスを取得
    git_dir="$(git rev-parse --git-dir)"

    # commondirを辿ってメインリポジトリの.gitディレクトリを取得
    # commondir の内容は相対パス(../..)または絶対パスの場合がある
    # gwqはデフォルトでメインリポジトリの外にworktreeを作成するため、基本的に絶対パスになる
    # cd で解決すれば両方に対応できる
    common_git_dir="$(cd "$git_dir/$(cat "$git_dir/commondir")" && pwd)"

    # メインリポジトリのルートパスを取得
    main_repo_root="$(dirname "$common_git_dir")"

    # symlink対象のファイルパターン. symlinkを貼りたいものを適宜書いて下さい.
    file_patterns=".env*"

    # 各パターンについてメインリポジトリ内を検索
    for pattern in $file_patterns; do

        # findコマンドでファイルを検索(仮で3階層にしてます)
        find "$main_repo_root" -maxdepth 3 -name "$pattern" -type f \
            ! -path "*/.git/*" 2>/dev/null | while read source_file; do

            # メインリポジトリルートからの相対パスを計算
            rel_path="${source_file#$main_repo_root/}"

            # worktree内での配置先パス(メインリポジトリと同じ相対位置)
            target_file="./$rel_path"

            # 配置先のディレクトリパス
            target_dir="$(dirname "$target_file")"

            # ディレクトリが存在しない場合は作成
            # .gitignore対象だけが存在するディレクトリはgitが作らないため(env/.env_sampleなど)
            mkdir -p "$target_dir"

            # symlink作成(既に存在する場合はスキップ)
            if [ ! -e "$target_file" ]; then
                ln -s "$source_file" "$target_file"
                echo "Linked: $rel_path"
            fi
        done
    done
fi

既存リポジトリに適用する場合は、各リポジトリで以下を実行します。

git config core.hooksPath ~/.git-templates/hooks

gwqラッパー関数の実装

次に、gwq addコマンド実行時に手動でpost-checkoutフックを起動するため、~/.zshenvに以下のラッパーを定義しました。

このラッパー関数は以下の処理を行います:

  1. gwq add コマンドを通常通り実行
  2. 成功した場合、引数からブランチ名を抽出
  3. gwq get でworktreeのパスを取得
  4. post-checkoutフックを手動実行してsymlinkを生成
gwqラッパー関数の実装(クリックで展開)
gwq() {
    # gwq本体のパス
    local gwq_bin="/Users/$(whoami)/go/bin/gwq"

    # "gwq add" コマンドの場合のみ特別処理
    if [[ "$1" == "add" ]]; then
        # 1. まず通常通りgwqコマンドを実行
        "$gwq_bin" "$@"
        local exit_code=$?

        # 2. gwq addが成功した場合のみ、post-checkoutフックを手動起動
        if [[ $exit_code -eq 0 ]]; then
            # 引数からブランチ名を抽出
            local branch_name=""
            local -a args=("$@")
            local i=2  # $1は"add"なので、$2から検索開始

            # 引数をループして、ブランチ名を特定
            while [[ $i -le ${#args[@]} ]]; do
                local arg="${args[$i]}"

                # -b または --branch オプションの場合
                if [[ "$arg" == "-b" ]] || [[ "$arg" == "--branch" ]]; then
                    ((i++))
                    branch_name="${args[$i]}"
                    break
                # オプションではない(-で始まらない)最初の引数 = ブランチ名
                elif [[ "$arg" != -* ]]; then
                    branch_name="$arg"
                    break
                fi
                ((i++))
            done

            # ブランチ名が取得できた場合
            if [[ -n "$branch_name" ]]; then
                # gwq get でworktreeのパスを取得
                local worktree_path
                worktree_path="$("$gwq_bin" get "$branch_name" 2>/dev/null)"

                # worktreeパスが有効な場合、post-checkoutフックを手動実行
                if [[ -n "$worktree_path" ]] && [[ -d "$worktree_path" ]]; then
                    # フック引数: dummy dummy 1
                    # - $1 (prev_head): dummy(使わない)
                    # - $2 (new_head): dummy(使わない)
                    # - $3 (branch_checkout): 1(ブランチチェックアウトを示す)
                    (cd "$worktree_path" && ~/.git-templates/hooks/post-checkout dummy dummy 1) >/dev/null 2>&1
                fi
            fi
        fi

        return $exit_code
    else
        # "gwq add" 以外のコマンドはそのまま実行
        "$gwq_bin" "$@"
    fi
}

動作確認

実際に動作を確認してみましょう。

# worktreeを作成
gwq add -b feature/new-feature

# 作成されたworktreeに移動
cd $(gwq get feature/new-feature)

# symlinkが作成されていることを確認
ls -la | grep ".env"

これで環境変数ファイルのコピー問題は解決しました。

実装後に知ったよりスマートなアプローチ

この実装をした後、wtpというツールの存在を知りました。

製作者ご本人による詳しい解説記事はこちらになります。

https://zenn.dev/satococoa/articles/f93f34f0e13696

wtpはgit worktreeの操作を便利にラップしたCLIツールで、以下の機能を持っています:

  • worktree作成後に.envコピーやnpm installを自動実行
  • worktreeとブランチの削除を一発で実行
  • post_create hook で環境構築を自動化

特に.envの自動コピーをpost_create hookで実現している点が非常にスマートで素敵です。

このように、自分で実装した後に他の方のアプローチを知るというのは、とても勉強になる体験でした!
(それと同時に調査不足を反省しました)

まとめ

本記事では、Git Worktreeとgwqを使った開発環境の構築方法と、実運用での課題とその解決策を紹介しました。

解決できたこと

  • Git Worktreeで複数ブランチを同時に扱える環境を構築
  • gwqでworktree運用を快適に
  • symlinkの自動化で環境変数ファイルのコピー作業を削減

特にAI Agentを活用した開発では、複数のworktreeを並行して使うシーンが増えています。今回紹介したsymlinkの仕組みで、環境変数ファイルの手動コピーという煩わしさからは解放されました。

ちなみに、並列化を実現できたとしても、実際の仕事の場面では1つ1つ自分で直列に確認作業をしています。人間自身がシングルスレッドで動く宿命からは逃れられないんだなあと思う日々です。

Discussion