作業の途中で「このプルリクを今すぐ見てほしい」と頼まれたり、本番で不具合が見つかって急ぎで修正が必要になったり。ブランチを切り替えたいけれど、いま書きかけのコードを片付けるのは面倒――そんなときに役立つのが git worktree です。1つのリポジトリから作業用のディレクトリをもう1つ生やして、別のブランチを同時に開いておけます。この記事では、基本の使い方から一覧・削除の方法、仕組み、git clone をもう1つ作る場合との違い、そしてつまずきやすいエラーへの対処までをまとめて解説します。
目次
git worktree は1つのリポジトリを複数のフォルダで開く仕組み
通常、1つのリポジトリで開けるブランチは1つだけです。git switch でブランチを切り替えると、いま見えているファイルがそっくり入れ替わります。これは「作業ツリー(working tree)」と呼ばれる、実際にファイルが展開されている場所がリポジトリに1つしかないためです。
git worktree は、この作業ツリーを別のディレクトリに追加で作るコマンドです。追加したディレクトリには別のブランチを展開できるので、元のディレクトリはそのまま触らずに、隣のディレクトリで別ブランチの作業ができます。リポジトリの本体(コミットやブランチの情報)は1つのままなので、どちらでコミットしても同じ履歴に積み上がります。
どんなときに便利か
書きかけのコードを片付けずに別ブランチを開ける
作業中に別ブランチを見たくなったとき、従来は変更を git stash で退避するか、中途半端な状態でコミットするかの二択でした。どちらもひと手間かかり、戻ってきたときに「どこまでやっていたか」を思い出す必要があります。
worktree を使えば、いまのディレクトリは編集中のまま放置しておき、別ディレクトリでレビュー対象のブランチを開けます。確認が終わったらそのディレクトリを消すだけで、元の作業には何の影響もありません。エディタを2つ並べて、片方で修正しながら片方で元の実装を参照する、といった使い方もできます。
ビルド成果物や node_modules を作り直さずに済む
ブランチを切り替えると package.json やロックファイルの内容が変わることがあり、そのたびに npm install やビルドをやり直すはめになります。規模の大きいプロジェクトでは、この待ち時間が地味に効いてきます。
worktree なら、追加したディレクトリが独自の node_modules やビルドキャッシュを持ちます(これらは通常 .gitignore されているためディレクトリごとに独立します)。元のディレクトリの成果物は消えないので、戻ってきたときもすぐ作業を再開できます。
git worktree add で作業ツリーを追加する
既存のブランチを別ディレクトリで開く
基本の形は git worktree add <パス> <ブランチ名> です。リポジトリの中で次のように実行します。
$ git worktree add ../review feature/foo
Preparing worktree (checking out 'feature/foo')
HEAD is now at 44c4006 first
これで、リポジトリの1つ上の階層に review というディレクトリが作られ、その中に feature/foo ブランチのファイル一式が展開されます。あとは cd ../review で移動すれば、そこは普通のリポジトリと同じように使えます。元のディレクトリのブランチや編集中のファイルは、いっさい影響を受けません。
パスは、リポジトリの外側を指定するのが基本です。リポジトリ内に作ると、そのディレクトリが元の作業ツリーに未追跡ファイルとして現れ、うっかりコミットする事故につながります。中に置きたい場合は .gitignore に登録しておきましょう。
-b で新しいブランチを作りながら追加する
hotfix のように「新しいブランチを切って、そこで作業を始めたい」場合は -b を付けます。ブランチの作成とディレクトリの用意が一度に済みます。
# main を起点に hotfix/login ブランチを作り、../hotfix に展開する
$ git worktree add -b hotfix/login ../hotfix main
Preparing worktree (new branch 'hotfix/login')
HEAD is now at 44c4006 first
最後の main は起点となるコミットで、省略すると現在の HEAD が使われます。ちなみに、ブランチ名を何も指定せずに git worktree add ../sandbox とだけ打った場合は、Git が気を利かせてディレクトリ名と同じ sandbox という新しいブランチを HEAD を起点に作ってくれます。ちょっとした試し書き用のディレクトリが欲しいときに便利です。
ブランチを作らずコミットだけ見たいときは –detach
「過去のタグの状態でビルドを確認したい」「このコミットの動作だけ見たい」といった場合は、ブランチを作らずに detached HEAD の状態で追加できます。
$ git worktree add --detach ../tmp-check v1.2.0
Preparing worktree (detached HEAD 44c4006)
HEAD is now at 44c4006 first
detached HEAD は「どのブランチにも属していない状態」なので、ここでコミットしてもブランチには残りません。確認だけして捨てる用途に向いています。残したくなったら、そのディレクトリの中で git switch -c <ブランチ名> を実行すればブランチにできます。
サブコマンドと主なオプション
git worktree は、サブコマンドを付けて使うコマンド群です。よく使うものを整理しておきます。
| サブコマンド・オプション | 意味 |
|---|---|
worktree add <パス> <ブランチ> | 指定パスに作業ツリーを追加する |
add -b <新ブランチ> <パス> | 新しいブランチを作りながら追加する |
add --detach <パス> <コミット> | ブランチを作らず detached HEAD で追加する |
add --force | すでに他所で使われているブランチでも強制的に追加する |
worktree list | 作業ツリーの一覧を表示する |
list --porcelain | スクリプトで扱いやすい機械可読な形式で出力する |
worktree remove <パス> | 作業ツリーを削除し、登録も消す |
remove --force | 未コミットの変更があっても強制的に削除する |
worktree prune | 実体のなくなった作業ツリーの登録情報を掃除する |
worktree move <パス> <新パス> | 作業ツリーを別の場所へ移動する |
worktree lock <パス> | 外付けドライブなど一時的に消えうる場所を保護し、prune の対象外にする |
一覧を見る・削除する・登録を掃除する
いま何が開いているか分からなくなったら git worktree list です。どのリポジトリのどのディレクトリからでも実行できます。
$ git worktree list
/Users/you/work/myapp 44c4006 [main]
/Users/you/work/hotfix 44c4006 [hotfix/login]
/Users/you/work/review 44c4006 [feature/foo]
/Users/you/work/tmp-check 44c4006 (detached HEAD)
左からパス、その作業ツリーの HEAD のコミットハッシュ、角括弧の中がチェックアウト中のブランチです。一番上の行が最初に git clone(または git init)したディレクトリで、これをメインの作業ツリーと呼びます。コミットを直接見ている場合は (detached HEAD) と表示されます。
用が済んだ作業ツリーは git worktree remove で片付けます。ディレクトリの削除と、リポジトリ側に残っている登録情報の削除を、まとめてやってくれます。
git worktree remove ../review
注意したいのは、remove が消すのは作業ツリー(ディレクトリ)だけで、ブランチは残るということです。そのブランチも不要なら、remove したあとで git branch -d feature/foo を実行してください。
もう1つ、git worktree prune というコマンドがあります。こちらは登録情報だけを掃除するもので、ディレクトリを手で削除してしまったときに使います(詳しくは後述します)。
.git がファイルになる仕組み
追加した作業ツリーの中を覗くと、.git がディレクトリではなくファイルになっていることに気づきます。中身は次の1行だけです。
$ cat ../review/.git
gitdir: /Users/you/work/myapp/.git/worktrees/review
つまり、この .git ファイルは「本体はあっちにあります」という道しるべです。Git はこのファイルを読んで、メインのリポジトリの .git/worktrees/<名前>/ を参照します。ここには、その作業ツリー専用の HEAD やインデックス(ステージングの状態)だけが置かれています。
重要なのは、コミットの実体もブランチも設定もすべてメインのリポジトリ側で共有されているという点です。作業ツリーごとに別々になるのは「いまどのブランチを見ているか」と「何をステージしているか」だけ。追加したディレクトリでコミットすれば、それは元のディレクトリからも git log で普通に見えますし、どこかで git fetch すれば結果はすべての作業ツリーに反映されます。
clone をもう1つ作るのとの違い
「別ディレクトリで別ブランチを開きたいだけなら、もう一度 git clone すればいいのでは」と思うかもしれません。実際それでも目的は果たせますが、いくつか差があります。
| 比較する点 | git worktree | もう1つ clone する |
|---|---|---|
| ディスク使用量 | 履歴は共有するので、増えるのは作業ファイルの分だけ | 履歴のコピーを丸ごともう1つ持つ |
| 用意する手間 | コマンド1回で完了、通信も発生しない | リポジトリ全体をダウンロードし直す |
| コミット・ブランチ | すべて共有され、どこで作っても全員から見える | それぞれ独立。受け渡しには push / fetch が必要 |
| fetch の手間 | 1回 fetch すれば全ディレクトリに反映される | ディレクトリごとに fetch が必要 |
| ローカル設定・stash | .git/config や stash を共有する | それぞれ別々に持つ |
要するに、同じプロジェクトを並行して進めたいだけなら worktree のほうが軽くて手間がかかりません。一方、片方を壊す実験に使いたい、まったく別の設定で動かしたいといった「完全に切り離したい」場面では、clone をもう1つ作るほうが安全です。
worktree が追加・削除できないとき
is already used by worktree at … と表示される
もっともよく出会うのがこのエラーです。
$ git worktree add ../dup feature/foo
Preparing worktree (checking out 'feature/foo')
fatal: 'feature/foo' is already used by worktree at '/Users/you/work/review'
Git は同じブランチを2か所で同時にチェックアウトできないという制約を持っています。同じブランチを別々の場所で編集すると、どちらの内容が正しいのか分からなくなるためです。同じ理由で、他の作業ツリーが使っているブランチへ git switch しようとしたときも同じメッセージが出ますし、git branch -d で削除しようとすると cannot delete branch 'hotfix/login' used by worktree at ... と拒否されます。
対処としては、まず git worktree list でどこが使っているかを確認します。そのディレクトリがもう不要なら remove し、まだ必要なら「そのディレクトリに移動して作業する」のが本来の使い方です。単に中身を見たいだけなら、--detach でコミットとして開けば制約にかかりません。
--force を付ければこの制約を無視して追加できますが、同じブランチを2か所で編集できてしまうため、通常はおすすめしません。片方でコミットしたあと、もう片方は古い状態のまま残り、混乱のもとになります。
contains modified or untracked files で削除できない
git worktree remove は、コミットしていない変更や未追跡ファイルが残っていると実行を止めます。
$ git worktree remove ../review
fatal: '../review' contains modified or untracked files, use --force to delete it
作業内容を消してしまう事故を防ぐための安全装置です。まずはそのディレクトリで git status を確認し、残したい変更があればコミットしてください。ビルド成果物や node_modules のような捨ててよいファイルが原因のときは、--force を付けて削除します。--force の使いどころは、このように「消えても困らないと確認できたとき」だけです。
なお、最初に clone したディレクトリ自身は削除できず、fatal: '.' is a main working tree というエラーになります。メインの作業ツリーはリポジトリの本体なので、これは仕様どおりの動きです。
削除したディレクトリが list に残っている
remove を使わずにディレクトリを直接削除すると、一覧に prunable という印が付いた行が残ります。
$ git worktree list
/Users/you/work/myapp 44c4006 [main]
/Users/you/work/tmp-check 44c4006 (detached HEAD) prunable
$ git worktree prune -v
Removing worktrees/tmp-check: gitdir file points to non-existent location
prunable は「実体が見当たらないので掃除できます」という意味です。git worktree prune を実行すれば登録が消えます。-v を付けると何を消したかが表示されるので、意図しないものまで消していないか確認できます。この登録が残ったままだと、同じブランチを使おうとしたときに先ほどの is already used by worktree が出続けます。削除は remove を使うか、手で消したら prune するのを習慣にしておきましょう。
まとめ
git worktree は、1つのリポジトリから作業用ディレクトリを追加して、複数のブランチを同時に開けるようにするコマンドです。git worktree add ../review feature/foo で既存ブランチを別ディレクトリに展開でき、-b を付ければ新しいブランチを作りながら追加、--detach ならブランチを作らずコミットだけを確認できます。追加したものは git worktree list で一覧でき、不要になったら git worktree remove、手で消してしまった場合は git worktree prune で登録を掃除します。
追加された側の .git は本体を指すファイルにすぎず、コミットもブランチも設定もすべて1つのリポジトリで共有されます。だからこそ、clone をもう1つ作るのと比べてディスクも通信も節約でき、fetch も1回で済むわけです。同じブランチを2か所で開けないという制約さえ押さえておけば、あとは難しいところはありません。作業を中断せずに別ブランチを触りたくなったときの選択肢として、覚えておくと重宝します。