🔗

AI開発の並列作業を快適にする git worktree + stgitの連携テクニック

に公開

はじめに

git worktree は、同じリポジトリの複数のブランチを並行して作業できる便利な機能です。

AI開発ツール(Claude Code、Cursor等)を使ったチーム開発では、共通の設定ファイル(CLAUDE.md、.claude/mcp.json等)を使いながらも、個人的に実験的な変更を加えたいことがあります。

従来の問題: 実験的な設定変更を誤ってpushしてしまうことを恐れて、コミットを避けていました。その結果、変更が追跡されず、worktree間で共有できないため、開発体験が損なわれていました。スクリプトでファイルをコピーしたり、vibe-kanbanのようなツールでファイル指定機能を使ったりする回避策もありますが、十分な改善とは言えませんでした。

stgit + Gitフックの解決: Stacked Git (stgit) を使えば、実験的な設定をパッチとして管理でき、pop/pushで簡単に切り替えられます。worktreeで並行作業する際も、stgitパッチはコミットとして引き継がれるため、実験的設定を共有できます。

しかし、ここで問題が発生します。 worktreeで新しいブランチを作成すると、コミットは引き継がれますが、stgitスタック情報(refs/stacks/)が引き継がれません。その結果、pre-pushフックが「このコミットはstgitパッチである」と判別できず、実験的な設定を誤ってpushしてしまう危険があります。

本記事では、post-checkoutフックでstgitスタック情報を自動コピーすることで、worktree間で実験的設定を安心してコミット・共有でき、並列作業の開発体験を大幅に向上させる方法を紹介します。

解決する問題

問題: worktreeでstgitパッチの誤pushを防げない

チーム開発では、CLAUDE.md、AGENTS.md、.claude/commands/、.claude/mcp.json などの共通設定ファイルがあります。

個人的に実験的な変更を加えたい場合、stgitパッチとして管理すれば簡単にpop/pushできて便利です:

# 実験的な設定変更をstgitパッチとして管理
$ vim CLAUDE.md  # プロンプトを実験的に調整
$ stg new experiment-config -m "Experimental AI config (do not push)"
$ stg refresh  # 変更内容をパッチに反映

# パッチはコミットとして記録される
$ git log -1 --oneline
a1b2c3d experiment-config

# 実験を終えたらpopして元の共通設定に戻せる
$ stg pop experiment-config

しかし問題が...

実験的な設定を誤ってpushすると、チームメンバーの環境に影響を与えてしまいます:

$ stg series
> experiment-config   # 実験的な設定変更
> work-in-progress    # 作業中のコード
$ git push            # うっかりpushしてしまう...
# チームメンバーの環境に実験的設定が混入!

pre-pushフックで防止できますが、worktreeで新しいブランチを作ると機能しません:

# メインリポジトリではpre-pushフックが機能
$ git push
❌ Cannot push: stgit patches are applied

# しかし、worktreeを作成すると...
$ git worktree add ../feature-work feature-branch
$ cd ../feature-work

# コミットは引き継がれているが、stgitスタック情報(refs/stacks/)がない
$ stg series
error: no series file or refs for applied stack found

# pre-pushフックが「パッチ適用中」を検出できず、誤pushを防げない!
$ git push  # 通ってしまう...

コミットは引き継がれるが、「それがstgitパッチである」という情報が失われるのが問題です。

解決策: Gitフックで自動化

この問題を、Gitフックを使って解決します。huskyで管理する前提で説明しますが、通常のGitフックでも同じ内容で動作します。

1. post-checkoutフック: stgitスタック情報の自動コピー

worktree作成時に、新しく作成されたworktree側でpost-checkoutフックが実行され、元のブランチのstgitスタック情報(refs/stacks/)を自動的にコピーします。

これにより、コミットだけでなく「それがstgitパッチである」という情報も引き継がれ、全てのworktreeでpre-pushフックが正しく機能します。

重要: このフックは新しいworktree側で動作するため、.husky/post-checkoutをメインリポジトリに配置しておく必要があります。なお、このフック自体もstgitパッチとして管理すれば、コミット履歴を汚さずに運用できます(詳細は後述)。

.husky/post-checkout:

# Auto-copy stgit stack refs for new worktree branches
prev_head=$1
new_head=$2
branch_checkout=$3

# Only run for branch checkouts
if [ "$branch_checkout" = "0" ]; then
    exit 0
fi

current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)

# Skip if on detached HEAD
if [ "$current_branch" = "HEAD" ] || [ -z "$current_branch" ]; then
    exit 0
fi

# Check if this is a worktree (has .git file instead of directory)
if [ -f .git ]; then
    # Skip if current branch already has a stack
    if ! git show-ref --verify --quiet "refs/stacks/$current_branch"; then
        # Find stgit stack from ancestor branch using merge-base
        for stack_ref in $(git show-ref | grep "refs/stacks/" | awk '{print $2}'); do
            # Extract branch name from refs/stacks/XXX or refs/stacks/feature/XXX
            branch_name=$(echo "$stack_ref" | sed 's|^refs/stacks/||')

            # Check if current branch was created from this branch
            if git merge-base --is-ancestor "$branch_name" "$current_branch" 2>/dev/null; then
                source_sha=$(git rev-parse "$stack_ref" 2>/dev/null)

                if [ -n "$source_sha" ]; then
                    echo "🔗 Setting up stgit stack for '$current_branch' from ancestor '$branch_name'"
                    git update-ref "refs/stacks/$current_branch" "$source_sha"
                    break
                fi
            fi
        done
    fi
fi

ポイント解説

1. worktreeの判定

.git がファイルかディレクトリかで判定します:

  • worktreeの場合: .git はファイル(gitdir参照を含む)
  • 通常のリポジトリ: .git はディレクトリ

2. 元のブランチの自動検出

git merge-base --is-ancestor を使用して、worktreeで作成した新しいブランチがどのブランチから派生したかを自動判定します。これにより、適切な元のブランチのstgitスタック情報を参照できます。

3. ブランチ名の階層構造に対応

ブランチ名に / が含まれる場合(例: feature/branch-name)、stgitのrefも refs/stacks/feature/branch-name のように階層構造になります。git show-ref でrefを列挙し、git rev-parse で実際のSHA1を取得することで対応しています。

2. pre-pushフック: パッチ適用中のpushを防止

stgitのパッチが適用されている状態でのpushを禁止します。

.husky/pre-push:

# Prevent pushing when stgit patches are applied

# Check if stgit is initialized for current branch
if stg series --applied 2>/dev/null | grep -q .; then
    echo "❌ Cannot push: stgit patches are applied"
    echo ""
    echo "Applied patches:"
    stg series --applied
    echo ""
    echo "Please pop all patches before pushing:"
    echo "  stg pop -a"
    exit 1
fi

実際の動作

ユースケース: 実験的設定の管理

チーム開発でClaude Codeなどのツールを使っている際の実際の使い方を紹介します。

1. stgitの初期化

# ブランチでstgitを初期化
$ stg init

2. 実験的な設定変更をstgitパッチで管理

# チーム共通の設定をベースに、個人的な実験を追加
$ vim CLAUDE.md              # プロンプトを実験的に調整
$ vim .claude/mcp.json       # 実験的なMCP Serverを追加
$ vim .claude/commands/test  # 試験的なカスタムコマンド

# 実験的変更をstgitパッチとして保存(pushしない)
$ stg new experiment-config -m "Experimental config (do not push)"
$ git add CLAUDE.md .claude/
$ stg refresh  # 変更内容をパッチに反映

$ stg series
> experiment-config  # 実験的設定のパッチ

3. worktreeで別の機能を開発(実験的設定も引き継がれる)

# 新しい機能開発用のworktreeを作成
$ git worktree add ../feature-auth auth-feature
🔗 Setting up stgit stack for 'auth-feature' from ancestor 'main'

$ cd ../feature-auth

# コミットは通常通り引き継がれている
$ git log -1 --oneline
a1b2c3d experiment-config

# さらに、stgitスタック情報もコピーされている!
$ stg series
> experiment-config  # 「これがstgitパッチである」という情報が保持されている

# worktreeでも実験的設定が有効なので、同じAI開発環境で作業できる
$ cat CLAUDE.md
# 実験的プロンプトが反映されている

# pre-pushフックが正しく機能する
$ git push
❌ Cannot push: stgit patches are applied
# 実験的設定の誤pushを防げる!

4. 実験を終えたらpopして共通設定に戻す

# 実験を終えたら、パッチをpopして共通設定に戻す
$ stg pop experiment-config
# チーム共通の設定に戻る

# 必要になったら再度実験を再開
$ stg push experiment-config
# 実験的設定が再び有効になる

パッチ適用中のpush防止

$ stg series
> work-in-progress
+ another-patch

$ git push
❌ Cannot push: stgit patches are applied

Applied patches:
> work-in-progress
+ another-patch

Please pop all patches before pushing:
  stg pop -a
husky - pre-push script failed (code 1)

# パッチをpopしてからpush
$ stg pop -a
$ git push
# 成功!

huskyへの組み込みとstgitパッチ管理

huskyを使っている場合は、以下のように設定します:

# フックファイルを作成
cat > .husky/post-checkout << 'EOF'
# (上記のpost-checkoutスクリプト)
EOF

cat > .husky/pre-push << 'EOF'
# (上記のpre-pushスクリプト)
EOF

# 実行権限を付与
chmod +x .husky/post-checkout
chmod +x .husky/pre-push

フック自体をstgitパッチで管理する

worktreeを使うか従来のワーキングコピーを使うかは個人・ツール・状況次第です。チームでworktreeを使わない人にとって、このフックは不要かもしれません。

そのような場合、フックファイル自体もstgitパッチとして管理しておくと便利です:

  • チームの共通リポジトリにコミットせず、個人的に使える
  • 必要に応じてフックを無効化(stg pop)できる
  • worktree間でフックの設定を自動共有できる
# フックをstgitパッチとして管理
$ stg new worktree-hooks -m "Git hooks for worktree + stgit integration"
$ git add .husky/post-checkout .husky/pre-push
$ stg refresh  # 変更内容をパッチに反映

# パッチ一覧で確認
$ stg series
> worktree-hooks

このパッチはpost-checkoutフックによって新しいworktreeにも自動的にコピーされるため、全てのworktreeで同じフック設定が有効になります。

stgitのインストール

stgitは各種パッケージマネージャーやソースからインストールできます。詳しいインストール方法は公式のインストールガイドを参照してください。

最新版のリリースはGitHubのリリースページから入手できます。

# インストール確認
stg --version
# Stacked Git 2.5.4

まとめ

本記事では、Stacked Gitとgit worktreeを組み合わせて、AI開発の並列作業体験を大幅に向上させる方法を紹介しました。

核心: 実験的設定を安心してコミットし、worktree間で快適に共有できる開発体験

手段: 2つのGitフックによる自動化

  1. post-checkoutフック: worktree作成時にstgitスタック情報(refs/stacks/)を自動コピー
  2. pre-pushフック: パッチ適用中の誤pushを防止

従来は誤pushを恐れてコミットを避けていましたが、stgitパッチ管理とGitフックの組み合わせにより、実験的設定を安心してコミットでき、全てのworktreeで共有できるようになります。

実現できること

チーム開発での実験的設定管理

  • ✅ チーム共通の設定ファイル(CLAUDE.md、.claude/mcp.json等)をベースに個人的な実験ができる
  • ✅ 実験的な変更をstgitパッチで管理し、pop/pushで簡単に切り替え
  • ✅ worktree間で実験的設定を共有し、同じAI開発環境で並行作業
  • ✅ 全てのworktreeで実験的設定の誤pushを防止

技術的な利点

  • ✅ git merge-base による賢い元ブランチの自動検出
  • ✅ スタック情報のコピーにより、worktree間でstgitの機能がフル活用できる
  • ✅ コミットは引き継がれるが、「stgitパッチである」という情報も保持

活用シーン

  • AI開発ツール(Claude Code、Cursor等)の設定をチームに影響なく実験
  • プロジェクト固有のカスタムコマンドやMCP Server設定を個人的に試行
  • 実験的な設定変更をコミット履歴に残さず管理
  • 複数の機能開発を並行して進める際も、実験的設定を維持

パッチベースのワークフローとworktreeの並行作業を組み合わせることで、より柔軟で効率的な開発が可能になります。特にAI開発ツールを活用する現代の開発フローにおいて、設定管理の課題を解決する強力な手法です。ぜひお試しください!

参考リンク

Discussion