TT Lab
はじめる
学ぶ 学習パス コース

Git実戦

作業を退避させず、机をもう一つ足す

TT Labで続きを見る

一言でいうと

git worktreeは、1つのリポジトリ(1つの.git)に作業ディレクトリを複数付ける機能です。コミット・ブランチ・オブジェクトはすべて共有し、チェックアウトした場所だけが変わります。そのため、作業中の内容をどこにも退避しないまま、別のブランチを開けます。

なぜ必要なのか

金曜日の午後に、こんなことが起きます。機能ブランチでファイル12個を半分ほど直してあるのに、本番で障害が起きて、リリースブランチにホットフィックスを入れる必要があります。普通は、次のようにします。

git stash
git switch release
# 고치고 커밋하고 올리고
git switch feature
git stash pop

この流れには、落とし穴が3つあります。1つ目は、git stashが追跡されていないファイルをデフォルトでは退避しないことです。新しく作ったファイルは、ブランチを切り替えてもその場に残り、別のブランチに紛れ込みます。2つ目は、stashが名前のないスタックなので、2、3個たまると何が何だかわからなくなり、stash popが衝突すると、その場で復旧する必要があることです。3つ目は、ブランチを切り替えた瞬間に、ビルドのアーティファクトと依存関係のディレクトリがまるごと無効になることです。大きなプロジェクトでは、この再ビルドが、ホットフィックス自体より長くかかります。

2つ目のコピーをgit cloneで作る方法もあります。そうすれば、上の3つは解決しますが、履歴をまるごともう一組ダウンロードする必要があり、2つのコピーのブランチが別々に動き始めます。片方で作ったコミットがもう片方にはないため、結局、2つの間でまた、pushとfetchを行うことになります。

git worktreeは、その中間を正確に埋めます。オブジェクトストアは1つなので履歴を再ダウンロードせず、ディレクトリは2つなので、ビルドのアーティファクトが混ざりません。

どう動くのか

git worktree add <경로> <브랜치>を実行すると、2つのものができます(プレースホルダーは、パスとブランチです)。

ここで重要な事実が1つ出てきます。HEADとインデックスはワークツリーごとに別ですが、ブランチの参照(refs/heads/*)はリポジトリ全体で共有されます。そのため、同じブランチを2か所でチェックアウトしようとすると、gitが拒否します。

$ git worktree add /tmp/dup main
fatal: 'main' is already used by worktree at '/home/me/repo'

これは不親切なのではなく、保護です。ブランチの参照が1つなのにワーキングツリーが2つあると、片方でコミットした瞬間に、もう片方は、自分が作っていないコミットの上に乗ることになり、インデックスと実際のファイルがずれます。本当に同じコミットを2か所で見たいなら、--detachで、ブランチなしで接続します。

後始末は、3つのコマンドに分かれます。

状況 コマンド 行うこと
作業が終わり、きれいに畳みます git worktree remove <경로> ディレクトリと管理ファイルを一緒に削除します
ディレクトリをすでにrm -rfしました git worktree prune 残った管理ファイルだけを削除します
外付けディスクのように、一時的に見えません git worktree lock pruneが触れないようにロックします

表のコードのプレースホルダーは、パスです。

rm -rfでディレクトリだけを削除すると、git worktree listに、その場所がprunableとしてずっと残ります。その状態で同じ名前で再びaddすると、「すでにある」と言われます。そして、pruneは管理ファイルだけを削除します。その場所で作ったブランチはそのまま残るため、ブランチまで整理するには、git branch -dを別に行う必要があります。

現場での姿

注意すべきこともあります。ワークツリーの中でgit gcが動くとき、他のワークツリーが参照するオブジェクトを生かしておくには、gitがそれらの場所を知っている必要がありますが、rm -rfで削除してpruneをしていない場所があると、その情報が古くなります。後始末を先延ばしにしないほうが、安全です。

次のラボですること

ワークツリーを3つ付けて、同じブランチを2回開こうとして拒否されるのを、自分の目で見ます。保存していない変更をそのままにして、ホットフィックスを別にコミットし、ディレクトリをrm -rfしたときにprunableと表示されることと、pruneのあとでもブランチが残ることを確認します。最後に、ロックされたワークツリーがremoveをどう拒否するかまで確認します。

公式ドキュメントは、git-worktreeです。