😎

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コマンドあるようです。

  1. git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]] [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]
  2. git worktree list [-v | --porcelain [-z]]
  3. git worktree lock [--reason <string>] <worktree>
  4. git worktree move <worktree> <new-path>
  5. git worktree prune [-n] [-v] [--expire <expire>]
  6. git worktree remove [-f] <worktree>
  7. git worktree repair [<path>…​]
  8. 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レポジトリを追加しようとしているよ! と怒られました。やはり追加はできないようですね。
git addしたら怒られた

VSCodeのSOURCE CONTROLには./testは表示されないのですが、これはVSCode側の事情があるのかもしれません。コード検索時も困りそうなので、最初からディレクトリは分けて、.gitignoreの対象にしたり、検索対象外にしたほうが良さそうです。ということで.worktreeというディレクトリの下に作り直してみます。.gitignoreに.worktreeを追加します。

無事、git statusには何も出なくなりました。

.worktree/testに作った

.git/worktrees/test というディレクトリが作られています。ここで管理されるようですね。worktrees/test/.git というファイルができているのでちょっと除いてみます。gitdir: /Users/itokohei/private-repos/git-worktree-test/.git/worktrees/testという内容になっていて、自分のパスが書かれているだけですね。ここはgit worktree管理対象だよ!と宣言するファイルとして動作しているのでしょうか。

ワーキングツリーを削除してみる

この worktreegit-initgit-clone によって準備された main worktree と対比して linked worktree と呼ばれています。 git worktree remove で削除できます。git worktree add <path><path> の最後の要素の branch が自動的に作られます。

まず branch が自動で作られることがわかりましたので見てみます。確かに作られています。
branchが作られている

git worktree remove test で削除してみます。worktree を削除しても branch 自体は残っていました。 switch も問題なくできます。

worktreeを削除してみる

既存のブランチをワーキングツリーにしてみる

上記で、ワーキングツリーとブランチを同時に作り、ワーキングツリーだけ削除した状態です。せっかくなので既存のブランチをworktreeに追加してみます。

git worktree add .worktree/test test します。test branchに居る状態だと追加できませんでした。
既存のbranchをworktreeに追加

最後にdetached HEADなworktreeを作ってみます。git worktree add -d .worktree/detached_head worktreeは作られたようです。.vscode.gitignore もコピーされていて、最初にtestを作った時と違いmainに合わせて進んでいます。
detached HEAD

ディレクトリが存在しないワーキングツリーを刈り込む

working treeが git worktree remove を使わずに削除された場合、関連する管理ファイルはガベージコレクトされることで自動的に削除されます。あるいはgit worktree prune で削除することもできます。

今回作ったdetached_headを消してみます。具体的には .worktree/detached_head を削除します。

削除前

削除後は .worktree/detached_head はなくなっていますが、.git/worktrees/deched_head は残っています。これが git worktree prune でき得るはずです。
削除後

消えました!
prune後

ディレクトリが存在しないワーキングツリーをロックする

linked worktree がポータブルデバイスやネットワーク上にあり、常にマウントされていない場合 git worktree lock で pruneの対象外にできます。

今回はプロジェクト内に .worktree ディレクトリを作りましたが、もしかしたら全く別の場所に作るのが想定されている使い方なのかもしれません。というのも git worktree add ../hotfix のように親ディレクトリにworktreeディレクトリを作るコマンドが紹介されているためです。例えば worktreeのディレクトリをアンマウント可能な場所においた場合、pruneやガベージコレクトにより管理情報が削除されると困るためlockするということですね。試してみます。lockするとpruneしても消えませんでした。

lock

unlockするとpruneで消えました。
unlock

コミット & マージしてみる

ここまででおおよそ雰囲気が掴めましたが、実際使う時はどんな感じなんでしょうか。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の拡張機能の設定は変える必要がありません。一見プロジェクトのコピーと変わりがなさそうですが、履歴自体はルートのプロジェクトに保存されるので余計なディスク容量も食いませんし、プロジェクト間でブランチのマージもできるので便利ですね。

commitからマージまでやってみる

コマンド

各コマンドの内容を詳細を整理します。

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できます。moveremove でも同様に必要になる場合があります。

-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-worktreeworktree です。前者はmainワーキングツリーのワーキングツリー毎の参照へのアクセスを可能とし、後者は全てのリンクされたワーキングツリーの参照を可能とします。

例えば main-worktree/HEAD あるいは main-worktree/refs/bisect/good は mainワーキングツリーの HEADrefs/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-parsegit-update-ref などのコマンドを使いましょう。

$ git rev-parse main-worktree/HEAD
bf2341bfff2d398b616de813b7c172102977a444

設定ファイル

初期状態では config ファイルは全てのワーキングツリーで共有されます。もしconfigの設定 core.barecore.worktree が共通の設定ファイルにあり、extensions.worktreeConfigdisabled になっていればmainワーキングツリーだけに適応されます。ワーキングツリー毎の設定を使うには worktreeConfigtrue とすることで実現できます。

$ git config extensions.worktreeConfig true

このモードでは git rev-parse --git-path config.worktree で指定されているパスに個別の設定が保存されます。git config --worktree を使用し設定ファイルの追加や変更ができる。 古いGitのバージョンではこの拡張機能は使えません。

$GIT_DIR/configcore.barecore.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