こちらは「Applibot Advent Calendar 2025」19日目の記事になります。
0. はじめに
AIを活用した並列開発では git worktree がとても便利です。私も日常的に利用しています。
この git worktree それ自体は2015年リリースのGit 2.5でリリースされており、10年以上前からあるコマンドです。
Git 2.5 リリース当時のGithubブログ(git worktree の紹介)
人力のみで開発していた際は影が薄かった気がしますが、AIによる並列開発が盛んになり再注目される流れになってきてますね。
ただこのコマンドには若干直感的でない挙動や制約もあるため、気をつけて利用する必要があります。
この記事ではコマンドの単純な利用方法ではなく、なぜそのような挙動や制約となるのかをgitの裏側を確認しつつ説明します。
1. コマンドの簡単な説明
活用方法などはすでに多くの記事があるので、簡単に git worktree を利用したコマンドを一連の流れで追っておきます。
例: git worktree を利用した一連の流れ
# 1. メインリポジトリ repo01 を作成
% mkdir repo01 && cd repo01
repo01 % echo "hoge" > hoge.txt
repo01 % git init
repo01 % git add . && git commit -m "init"
repo01 % ls -AF
.git/ hoge.txt
# 2. worktreeを作成し移動
repo01 % git branch branch-repo02
repo01 % git worktree add ../repo02 branch-repo02
repo01 % cd ../repo02
# 3. 何かしらの作業
repo02 % echo "fuga" > fuga.txt
repo02 % git add .
repo02 % git commit -m "Add fuga"
repo02 % ls -AF
.git fuga.txt hoge.txt
# 以降も作業は続く……
# 4. repo01 にて worktree の確認
repo01 % git worktree list
/path/refpuyo/repo01 1e12f6b [master]
/path/refpuyo/repo02 5a1214b [branch-repo02]
# 5. 不要になった worktree を削除
repo01 % git worktree remove ../repo02
こんな感じで、複数ディレクトリで並行作業ができるわけですね。
2. worktreeの制約
worktreeは便利な機能なのですが、いくつかの制約があります。
同じブランチを複数のworktreeで使用できない
# 4. の後などで以下を実行するとエラーになる
repo01 % git worktree add ../repo03 branch-repo02
Preparing worktree (checking out 'branch-repo02')
fatal: 'branch-repo02' is already used by worktree at '/path/refpuyo/repo02'
ディレクトリを直接削除してもゴミが残る
# 5. にて git worktree remove ではなく直接削除すると……?
repo01 % rm -rf ../repo02
# git worktree list に消したはずの repo02 が残る
repo01 % git worktree list
/path/refpuyo/repo01 1e12f6b [master]
/path/refpuyo/repo02 5a1214b [branch-repo02] prunable
# この状態では branch-repo02 を使った worktree を新しく作ることはできない
repo01 % git worktree add ../repo03 branch-repo02
Preparing worktree (checking out 'branch-repo02')
fatal: 'branch-repo02' is already used by worktree at '/path/refpuyo/repo02'
# git worktree prune を実行すると完全に消える
repo01 % git worktree prune
repo01 % git worktree list
/path/refpuyo/repo01 1e12f6b [master]
# branch-repo02 を使った worktree を新しく作ることが可能
repo01 % git worktree add ../repo03 branch-repo02
Preparing worktree (checking out 'branch-repo02')
HEAD is now at 5a1214b Add fuga
なぜこのような制約があるのでしょうか?
3. 内部実装の詳細
.gitの中身
worktreeがどのように動作しているか、.gitディレクトリの中身を見てみましょう。
通常のリポジトリの.git構造
# repo01 で tree した例(長いので一部編集して省略しています)
repo01 % tree -a
.
├── .git
│ ├── HEAD # 現在のブランチへの参照
│ ├── config # リポジトリ設定
│ ├── index # ステージングエリア
│ ├── logs/ # reflog
│ ├── objects/ # Gitオブジェクト(blob, tree, commit)
│ └── refs/ # ブランチとタグの参照
└── hoge.txt
worktree追加後の.git構造
worktreeを追加すると、メインリポジトリの.gitディレクトリにworktrees/ディレクトリが作成されます。
# worktree 追加して tree した例(長いので一部編集して省略しています)
repo01 % git worktree add ../repo02 branch-repo02
Preparing worktree (checking out 'branch-repo02')
HEAD is now at 5a1214b Add fuga
repo01 % tree -a
.
├── .git
│ ├── HEAD # 現在のブランチへの参照
│ ├── config # リポジトリ設定
│ ├── index # ステージングエリア
│ ├── logs/ # reflog
│ ├── objects/ # Gitオブジェクト(blob, tree, commit)
│ ├── refs/ # ブランチとタグの参照
│ └── worktrees # worktree
│ └── repo02
│ ├── HEAD # worktreeの現在のブランチ
│ ├── commondir # メインリポジトリへのパス
│ ├── gitdir # worktreeの.gitへのパス
│ ├── index # worktreeのステージングエリア
│ ├── logs/ # worktreeのreflog
│ └── refs
└── hoge.txt
各worktreeディレクトリには、こんなファイルが含まれます:
- HEAD: そのworktreeが現在チェックアウトしているブランチやコミット
- index: そのworktree独自のステージングエリア
-
gitdir: そのworktreeの作業ディレクトリにある
.gitファイルへのパス -
commondir: メインリポジトリの
.gitディレクトリへのパス - logs: そのworktreeのreflog
worktree側の.git
実は、worktree側には通常の.gitディレクトリの代わりに.gitファイルが配置されます。
# repo02 で tree した例
repo02 % tree -a
.
├── .git # こいつはファイル!!
├── fuga.txt
└── hoge.txt
# .git の中身はメインリポジトリにある自身のworktreeへの参照
repo02 % cat .git
gitdir: /path/refpuyo/repo01/.git/worktrees/repo02
# ちなみにメインリポジトリのgitdirファイルの中身はこんな感じ
repo01 % cat .git/worktrees/repo02/gitdir
/path/refpuyo/repo02/.git
このように、メインリポジトリとworktreeはお互いを参照しあっている訳ですね。
共有されるもの、されないもの
worktreeの仕組みを理解する上で重要なのが、メインリポジトリと各worktreeで何が共有され、何が独立しているかです。
| 項目 | 共有/独立 | パス | 内容 |
|---|---|---|---|
| objects/ | 共有 | repo01/.git/objects/ |
コミット、ツリー、ブロブなどのGitオブジェクト |
| refs/ | 共有 | repo01/.git/refs/ |
ブランチ、タグの参照 |
| config | 共有 | repo01/.git/config |
リポジトリの設定 |
| HEAD | 独立 |
repo01/.git/HEADrepo01/.git/worktrees/repo02/HEAD
|
現在チェックアウトしているブランチ |
| index | 独立 |
repo01/.git/indexrepo01/.git/worktrees/repo02/index
|
ステージングエリア |
| logs/ | 独立 |
repo01/.git/logs/repo01/.git/worktrees/repo02/logs/
|
reflog(操作履歴) |
共有されるものによる影響
-
objectsの共有により
- worktree作成時にオブジェクトをコピーする必要がない(高速!)
- 同じオブジェクトを複数持たない(省ディスク!)
-
refsの共有により
- どのworktreeからでもブランチの作成・削除が可能
- 全worktreeで同じ履歴を共有(一貫性!)
-
configの共有により
- リポジトリ設定が全worktreeで統一される
共有されないものによる影響
-
HEADの独立により
- 各worktreeで異なるブランチをチェックアウト可能(並行作業可能!)
-
indexの独立により
- 各worktreeで独立してファイルをステージング可能
-
logsの独立により
- 各worktreeの操作履歴を個別に追跡可能
制約の仕組み
内部実装を理解すると、2 worktreeの制約の理由がわかります。
同じブランチを使えない理由
ブランチの実体は repo01/.git/refs/heads/<branch-name> のような共有された1つのファイルです。
もし2つのworktreeで同じブランチを操作できると、その単一のrefsファイルを奪い合う競合状態になり、整合性が壊れてしまいます。
そのためGitはすべてのworktreeのHEADをスキャンし、同じブランチが使われていないかを厳格にチェックしています。
該当のgit実装: die_if_checked_out()
直接削除でゴミが残る理由
worktree側の.gitで見た通り、相互参照の仕組みが原因です。
メインリポジトリのgitdirからの参照:
repo01/.git/worktrees/repo02/gitdir → repo02/.git
worktree側の.gitからの参照:
repo02/.git → repo01/.git/worktrees/repo02
rm -rf などでworktree側を消したりしても駄目ですし、メインリポジトリのディレクトリ名を変更したりなどしても相互参照が崩れてしまいます。必ず git worktree move <worktree> <new-path> などのコマンドで移動しましょう。
4. まとめ
この記事では、git worktreeの内部実装を深掘りして、その挙動や制約の理由を解説しました。
- オブジェクトをコピーせず、参照を作るだけなので瞬時に作成可能(高速!)
-
objects/などを共有するため、リポジトリのクローンより効率的(省ディスク!) - 各worktreeが独立した
HEADとindexを持つため、ブランチ切り替え不要(並行作業可能!)
などの利点のある git worktree ですが、以下のような制約があります。
-
.git/refsを共有するため、同じブランチを複数のworktreeで使用できない - 相互参照の仕組みのため、ディレクトリを直接削除したりディレクトリ名を直接変更するなどはできない
- パス関連操作はOS操作(rm, mv)ではなくGitコマンドを使うのが鉄則
内部実装を理解することで、worktreeの挙動に迷わず自信を持って活用できるようになります。
とくにAIによる並行開発に活躍するので、日常の開発フローに取り入れてみてください。
参考リンク
- The Github Blog - Git 2.5, including multiple worktrees and triangular workflows
- Git - git-worktree Documentation
- GitHub - git/git
株式会社アプリボットでは、 エンジニアをはじめ全職種で積極採用中 です。
記事を読んで少しでも興味を持たれた方やお話を聞きたい方は、是非お気軽にご連絡ください!