Git Worktree:複数ブランチを同時に扱う便利な機能
はじめに
昨今はAIエージェントに作業をさせるためにGit Worktreeというものを使うようです。Git WorkTreeについて知らなかったので整理してみました。以下の記事では「必須技術!」と言われていますね。
Claude Code、GitHub Copilot、Cursor など、様々な AI ツールが同時に複数のタスクを並行して処理することを可能にしました。しかし、従来の Git ワークフローでは、ブランチ間の切り替えによる作業の中断や、複数のタスクを同時進行する際のコンフリクトが課題となっています。
詳しくはリンク先を読んでみてください。
Git Worktreeとは
Git Worktreeについては以下の記事が詳しかったです。
しかしながら体感しないとわからない(愚者😢な)ので原典に当たりながら試してみます。特にワーキングツリーごとにディレクトリで管理するという表現がしっくり来ていません。というのも、ディレクトリ、ファイルを追加したらそれって単純にgitの管理対象ファイルになっちゃうんじゃないの?という疑問が解決できません。どういう形で管理されるのか理解を進めたいと思います。
Git Worktreeのユースケース
以下のユースケースがわかりやすかったので最初に書いておきます。
あなたは今、リファクタリング作業の真っ最中ですが、上司がやってきて「すぐに何かを修正しろ」と要求してきました。通常であれば git stash を使って変更内容を一時的に保存するところですが、作業ツリーが新規ファイル・移動・削除などで散らかりすぎていて、何かを壊してしまうリスクを取りたくありません。その代わりに、一時的なリンクされた worktree を作って緊急対応の修正作業を行い、作業が終わったらその worktree を削除し、元のリファクタリング作業に戻ります。
# `emergency-fix`というブランチを`../temp`ディレクトリに`master`ブランチをベースに作成する
$ git worktree add -b emergency-fix ../temp master
# ../tempに移動する。`emergency-fix`ブランチが`checkout`される
$ pushd ../temp
# ... hack hack hack ...
#`emergency-fix`にコミット
$ git commit -a -m 'emergency fix for boss'
# 元の場所に戻る
$ popd
# 用が済んだらディレクトリは削除する
$ git worktree remove ../temp
以下の情報を読み解いて整理していきます。
git-worktree
複数のワーキングツリーを管理する。
コマンド
以下の8コマンドあるようです。
- git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]] [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]
- git worktree list [-v | --porcelain [-z]]
- git worktree lock [--reason <string>] <worktree>
- git worktree move <worktree> <new-path>
- git worktree prune [-n] [-v] [--expire <expire>]
- git worktree remove [-f] <worktree>
- git worktree repair [<path>…]
- git worktree unlock <worktree>
add,list,move,removeあたりはなんとなく動作がわかりますが、それ以外は馴染みがないですね。
概要
gitレポジトリは複数のワーキングツリーをサポートしていて、同時に複数の2つ以上のブランチをチェックアウトできます。
ワーキングツリーを追加してみる
git worktree add
を実行するとレポジトリに関連付けされた新しいワーキングツリーが作られます。同時に作成したワーキングツリーを管理するメタデータも生成されます。
やってみます。事前にgitレポジトリを作っておきます。
ワーキングツリー test
を作成します。
git worktree add test
作成されました。
指定したtest
というパスにコードREADME.md
がコピーされ、.git
ファイルが作られています。git status
するとtest/
がUntracked file
になっています。これは想定外。Worktree
のディレクトリ自体をgitにファイルとして追加できちゃうのか?
git add test/
してみました。gitレポジトリの中にgitレポジトリを追加しようとしているよ!
と怒られました。やはり追加はできないようですね。
VSCodeのSOURCE CONTROL
には./test
は表示されないのですが、これはVSCode
側の事情があるのかもしれません。コード検索時も困りそうなので、最初からディレクトリは分けて、.gitignoreの対象にしたり、検索対象外にしたほうが良さそうです。ということで.worktree
というディレクトリの下に作り直してみます。.gitignoreに.worktree
を追加します。
無事、git statusには何も出なくなりました。
.git/worktrees/test
というディレクトリが作られています。ここで管理されるようですね。worktrees/test/.git
というファイルができているのでちょっと除いてみます。gitdir: /Users/itokohei/private-repos/git-worktree-test/.git/worktrees/test
という内容になっていて、自分のパスが書かれているだけですね。ここはgit worktree管理対象だよ!と宣言するファイルとして動作しているのでしょうか。
ワーキングツリーを削除してみる
この
worktree
はgit-init
やgit-clone
によって準備されたmain worktree
と対比してlinked worktree
と呼ばれています。git worktree remove
で削除できます。git worktree add <path>
で<path>
の最後の要素のbranch
が自動的に作られます。
まず branch が自動で作られることがわかりましたので見てみます。確かに作られています。
git worktree remove test
で削除してみます。worktree
を削除しても branch
自体は残っていました。 switch
も問題なくできます。
既存のブランチをワーキングツリーにしてみる
上記で、ワーキングツリーとブランチを同時に作り、ワーキングツリーだけ削除した状態です。せっかくなので既存のブランチをworktreeに追加してみます。
git worktree add .worktree/test test
します。test
branchに居る状態だと追加できませんでした。
最後にdetached HEAD
なworktreeを作ってみます。git worktree add -d .worktree/detached_head
worktreeは作られたようです。.vscode
や.gitignore
もコピーされていて、最初にtest
を作った時と違いmain
に合わせて進んでいます。
ディレクトリが存在しないワーキングツリーを刈り込む
working treeが
git worktree remove
を使わずに削除された場合、関連する管理ファイルはガベージコレクトされることで自動的に削除されます。あるいはgit worktree prune
で削除することもできます。
今回作ったdetached_head
を消してみます。具体的には .worktree/detached_head
を削除します。
削除後は .worktree/detached_head
はなくなっていますが、.git/worktrees/deched_head
は残っています。これが git worktree prune
でき得るはずです。
消えました!
ディレクトリが存在しないワーキングツリーをロックする
linked worktree がポータブルデバイスやネットワーク上にあり、常にマウントされていない場合
git worktree lock
で pruneの対象外にできます。
今回はプロジェクト内に .worktree
ディレクトリを作りましたが、もしかしたら全く別の場所に作るのが想定されている使い方なのかもしれません。というのも git worktree add ../hotfix
のように親ディレクトリにworktreeディレクトリを作るコマンドが紹介されているためです。例えば worktreeのディレクトリをアンマウント可能な場所においた場合、pruneやガベージコレクトにより管理情報が削除されると困るためlockするということですね。試してみます。lockするとpruneしても消えませんでした。
unlockするとpruneで消えました。
コミット & マージしてみる
ここまででおおよそ雰囲気が掴めましたが、実際使う時はどんな感じなんでしょうか。branchの切り替えのためのstashなどをしなくて良いという触れ込みなのでやってみます。まず作業するbranchに移動して見ようと思いますが。。。git checkout test
で移動ができません!
$ git checkout test
fatal: 'test' is already checked out at '/Users/itokohei/private-repos/git-worktree-test/.worktree/test'
どうやらworktree
はディレクトリを移動することでブランチの移動ができるようです。確かにこれならいくら編集中のファイルがワーキングツリーにあろうがその他のワーキングツリーには影響がないですね。cd で .workspace/test
に移動、ファイル変更、コミット、main
に戻り git merge test
できました。ここまで見てきて、worktreeのディレクトリはプロジェクト外に追い出すのが良さそうだと思いました。そして新しく作ったブランチを別のプロジェクトとしてVSCodeで開けばよいですね。そうすれば SOURCE CONTROL
などVSCodeの拡張機能の設定は変える必要がありません。一見プロジェクトのコピーと変わりがなさそうですが、履歴自体はルートのプロジェクトに保存されるので余計なディスク容量も食いませんし、プロジェクト間でブランチのマージもできるので便利ですね。
コマンド
各コマンドの内容を詳細を整理します。
add <path> [<commit-ish>]
<path>
のworktreeを作り <commit-ish>
をチェックアウトします。 新しいworktreeは現在のレポジトリにリンクされます。worktree2個別のファイル以外を共有します。
list
worktreeの詳細をリスト表示します。"detached HEAD" や "locked" "prunable" などの状態を表示します。
lock
worktreeが外部デバイスなどのマウントが必要となるような場所にある場合でもprune
されないようにします。
move
worktreeの位置を変更できます。ただしgit submoduleを保つ場合はmoveでは移動できません。その場合は代わりにrepairを使います。
prune
.worktreesディレクトリ以下の不要となった情報を削除します。
remove
worktreeを削除します。cleanな状態ではないディレクトリやsubmoduleを保つ場合は--force
オプションが必要です。
repair [<path>...]
外部要因で壊れたり最新の状態ではなくなった管理情報を修復します。例えばmain worktreeが移動された場合などに linked worktrees を配置できなくなります。新しく移動した場所でrepair
をするとコネクションが再生成されます。
unlock
lockしたworktreeをunlockします。
オプション
-f
--force
デフォルトでは add
は 以下の場合にworktreeの作成を拒否します。他のワーキングツリーで使われているブランチ名の場合。<paht>
がマニュアルで削除されるなどして存在しない場合。この場合-f
で強制的にworktreを作成します。lockされている場合はオプションを2回付与することでadd
できます。move
、remove
でも同様に必要になる場合があります。
-b
<new-branch>
-B
<new-branch>
add
といっしょに使うことで <new-branch>
という名前のブランチを作ります。 -b
、-B
がない場合はブランチ名はpathの最後の要素となります。-b
はブランチが存在する場合はエラーとなり、-B
ではブランチがリセットされます。
-d
--detach
add
といっしょに使うと detached HEAD
なworktreeを作ることができます。
--[no-]checkout
add
した時の自動チェックアウトを無効化します。"Sparse checkout"するなどカスタマイズしたい時に使います。
--[no-]guess-remote
worktree add <path>
した時に新しいブランチを HEAD
から作るのではなく、<path>のbasenameが remote
にある場合にはそれを リモートブランチとします。worktree.guessRemote
でデフォルトの動作を設定できます。
--[no-]relative-paths
worktreeの位置を相対パスで指定可能になります。デフォルトは絶対パスです。worktree.useRelativePaths
で設定ができ、パスが正しくても repair
コマンドで現在のパスを更新できます。
--[no-]track
新しいブランチを作る時にリモートブランチにある同名のブランチと紐づけるかどうかを指定します。
--lock
worktree作成後にlockします。git worktree add && git worktree lock
と同じですが、同時実行による状態の不整合が起きません。
-n
--dry-run
prune
で使います。実際には削除を行わずに、削除対象のレポートだけ行います。
--orphan
add
といっしょに使います。index情報が空のブランチが作られます。gh-pages等の既存の履歴情報を必要としないブランチを作る場合などに使う。
--porcelain
list
といっしょに使います。スクリプトが解釈しやすい形で出力をします。
-z
list --porcelain
を使う場合に使用します。改行コードが含まれる場合でも解析可能とするために、worktree間の区切り文字をNUL
にします。
-q
--quiet
add
を使う時のフィードバックメッセージを抑制します。
-v
--verbose
prune
といっしょに使い、全ての削除情報を通知します。
list
といっしょに使うと、worktreesに関する追加の情報を出力します。
--expire <time>
prune
といっしょに使い、<time>
より古いworktreesを削除します。
list
といっしょに使い、、<time>
より古いworktreesをprunable
として表示します。
--reason <string>
lock
といっしょに使うとlockが必要な理由を残します。
参照について
Git Worktreeを使う上で、複数のワーキングツリーがどのように参照を共有・分離しているかを理解することは重要です。特に、設定ファイルやHEADといった情報がどのように管理されているかを知ることで、より安全かつ効率的にGit Worktreeを活用できます。
複数のワーキングツリーを使う場合いくつかの参照は全てのワーキングツリーで共有されますが、いくつかはワーキングツリー固有の参照です。例えばHEAD
はワーキングツリー毎に異なります。ここでは参照の使い方とワーキングツリー間で参照を使う方法を説明します。
一般的に全ての疑似参照はワーキングツリーごとに管理されて、refs/
で始まるすべての参照は共有されます。疑似参照とはHEAD
等の$GIT_DIR
直下で管理されるもので、$GIT_DIR/refs$
とは異なります。しかしrefs/bisect
, refs/worktree
, refs/rewritten
は例外的に共有されません。
ワーキングツリー毎の参照は2つの特別なパスをつかって参照することができます。main-worktree
と worktree
です。前者はmainワーキングツリーのワーキングツリー毎の参照へのアクセスを可能とし、後者は全てのリンクされたワーキングツリーの参照を可能とします。
例えば main-worktree/HEAD
あるいは main-worktree/refs/bisect/good
は mainワーキングツリーの HEAD
と refs/bisect/good
それぞれに相当します。同じ様に worktrees/foo/HEAD
あるいは worktrees/bar/refs/bisect/bad
は $GIT_COMMON_DIR/worktrees/foo/HEAD
と $GIT_COMMIT_DIR/worktrees/foo/bar/refs/bisect/bad
に相当します。参照にアクセスするために $GIT_DIR
ディレクトリを参照するのはよくありません。その代わりにgit-rev-parse
や git-update-ref
などのコマンドを使いましょう。
$ git rev-parse main-worktree/HEAD
bf2341bfff2d398b616de813b7c172102977a444
設定ファイル
初期状態では config
ファイルは全てのワーキングツリーで共有されます。もしconfigの設定 core.bare
や core.worktree
が共通の設定ファイルにあり、extensions.worktreeConfig
が disabled
になっていればmainワーキングツリーだけに適応されます。ワーキングツリー毎の設定を使うには worktreeConfig
を true
とすることで実現できます。
$ git config extensions.worktreeConfig true
このモードでは git rev-parse --git-path config.worktree
で指定されているパスに個別の設定が保存されます。git config --worktree
を使用し設定ファイルの追加や変更ができる。 古いGitのバージョンではこの拡張機能は使えません。
$GIT_DIR/config
に core.bare
や core.workspace
の設定がある場合はメインワーキングツリーのconfig.worktree
に移動する必要があります。詳細はgit-configを参照してください。
まとめ
Git Worktreeについて理解できました!Git Worktreeを使うことで、複数のブランチを同時にチェックアウトし、それぞれのブランチで独立して作業を進めることができます。これにより、緊急の修正対応や機能開発を、現在の作業を中断することなく、安全かつ効率的に行うことが可能になります。
Git Worktreeのメリット
- 複数ブランチでの同時作業: 複数のブランチを同時にチェックアウトし、それぞれのブランチで独立して作業を進めることができます。
Git Worktreeの注意点
- ワークツリーのディレクトリは、リポジトリのルートディレクトリとは別の場所に作成することを推奨します。
- Git submoduleとの連携には注意が必要です。
Git Worktreeを使いこなして、より快適なGitライフを送りましょう!
環境
動作確認した環境は以下です。
- MacBook Pro
- 14インチ 2021
- チップ:Apple M1 Pro
- メモリ:32GB
- macOS:15.5(24F74)
Discussion