【小ネタ】Git Worktreeを導入してみた

目次
はじめに
こんにちは、CloudBuildersのsugawaraです。
最近、Gitを利用するときに不便に感じることが増えました。ある作業中に別の割り込み作業が入ったり、忘れたころに以前のプルリクの修正依頼がやってきたり。つまり、同一のリポジトリ内で複数ブランチを並行して作業しています。
これまでは毎回作業途中のコードをすべてcommitしたり、stashして一時退避させたりして対応していました。(そもそも最初の頃は新しくリポジトリをcloneして別々に作業することも)
ただ、毎回これらを実行するのもなかなか面倒なので、どうにかできないかと思っていました。そんなときに見つけた解決策の一つが、Git Worktreeです。これを使えば、作業中のコードをcommitやstashせずに済ませることができます。今回はそんな便利なGit Worktreeを紹介します。
Git Worktreeとは
git worktreeコマンドは、Gitの標準機能(!)の1つであり、Gitがインストールされていればすぐに利用できます。1つのGitリポジトリから複数の作業ディレクトリ(ワークツリー)を作成することができます。これにより、同一リポジトリの複数のブランチを自由に行き来し、同時に作業することができます。
下記の図がとてもわかりやすく構成を表現しています。本来であれば、各リポジトリにワークツリーは一つ(main/master)ですが、git worktreeコマンドを使うことで、featureディレクトリ配下にそれぞれブランチを作成することができます。

(https://www.gitkraken.com/learn/git/git-worktree)
細かなコマンドについては下記の公式ドキュメントをご参照ください。
https://git-scm.com/docs/git-worktree
やってみた
今回はdevelopブランチにて、別のfeatureブランチを切って作業し、その内容をdevelopブランチにマージするところまでやっていきます。
作業用ディレクトリを作成し、適当なGitHubのリポジトリをcloneします。中身はただのREADMEファイルだけでした。
$ mkdir git-worktree-practice && cd git-worktree-practice
$ git clone https://github.com/octocat/Hello-World.git
developブランチを作成して移動します。
$ git branch -c develop
$ git branch
* develop
master
git worktree使用前の状態を確認します。使うのはgit worktree listコマンドです。まだdevelopのみであることがわかります。
$ git worktree list
/home/sugawara/learn/git-worktree-practice/Hello-World 7fd1a60 [develop]
次に、git worktree addコマンドを実行して、worktree-featureAディレクトリ内にfeature/featureAブランチを作成します。そして、再度worktreeリストを表示します。結果にfeature/featureAブランチが追加されたのがわかります。
$ git worktree add -b feature/featureA ../worktree-featureA develop
Preparing worktree (new branch 'feature/featureA')
HEAD is now at 7fd1a60 Merge pull request #6 from Spaceghost/patch-1
$ git worktree list
/home/sugawara/learn/git-worktree-practice/Hello-World 7fd1a60 [develop]
/home/sugawara/learn/git-worktree-practice/worktree-featureA 7fd1a60 [feature/featureA]
git branchコマンドでも確認してみると、ブランチが増えているのがわかります。
$ git branch
* develop
feature/featureA
master
なお、developブランチはHello-worldディレクトリ内ですが、feature/featureAはworktree-featureAディレクトリ内になっているため、ディレクトリを移動することでブランチを変えることができます。
なお、developの前についている+マークは、別ワークツリーで使用中のブランチという意味のようです。
# featureAディレクトリへ移動
$ cd ../worktree-featureA/
sugawara@cb-sugawara:~/learn/git-worktree-practice/worktree-featureA$
# ブランチの確認
$ git branch
+ develop
* feature/featureA
master
まずfeatureAブランチでファイルを追加してコミットします。
$ echo "This is from worktree-featureA branch!" > feature.txt
$ git add feature.txt
$ git commit -m "Add feature.txt from featureA branch"
続いて、developブランチに戻ってファイルを追加してコミットしてみます。
# developブランチへ移動
$ cd ../Hello-World/
sugawara@cb-sugawara:~/learn/git-worktree-practice/Hello-World$
$ echo "This is develop" > develop.txt
$ git add develop.txt
$ git commit -m "Add develop.txt from develop branch"
これで同一のリポジトリだが、異なるブランチからコミットをしたことになります。
次にdevelopへの変更をマージしていきます。
$ git merge feature/featureA
Merge made by the 'ort' strategy.
feature.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 feature.txt
featureAブランチからの変更がdevelopブランチに反映されていることがわかります。
最後に、不要になったワークツリーを削除します。使うのはgit worktree removeコマンドです。
# 削除前
$ git worktree list
/home/sugawara/learn/git-worktree-practice/Hello-World b1e93fe [develop]
/home/sugawara/learn/git-worktree-practice/worktree-featureA 84a5c5b [feature/featureA]
sugawara@cb-sugawara:~/learn/git-worktree-practice/Hello-World$ git worktree remove ../worktree-featureA
# 削除後
sugawara@cb-sugawara:~/learn/git-worktree-practice/Hello-World$ git worktree list
/home/sugawara/learn/git-worktree-practice/Hello-World b1e93fe [develop]
注意点
今回はターミナル上のコマンド操作のみで進めていきました。そのため、ディレクトリ移動だけでブランチを変更し、ファイルを追加することができました。
しかし、VSCodeなどのIDEのGUIではブランチを変更できません。Git Worktreeでは、「1つのブランチは1つのワークツリーでしか使えない」という制約があるため、VSCodeの左下のブランチ切り替えメニューから他のブランチに変更しようとすると、以下のようなエラーになることがあります。
対処方法としては、code .コマンドなどで別でVSCodeウインドウを立ち上げることです。これにより、同時並行で同一リポジトリの別ブランチを修正することが可能です。
また、Git Worktree管理用の拡張機能もあり、それを使えばわざわざ別ウインドウを立ち上げる必要もないです。いくつかの種類が出ており、決定版がないようなので、それぞれ拡張機能を試してみたいと思います。
おわりに
今回はgit worktreeを紹介しました。毎回stashやcommitをせずに、割り込み作業ができるのはとてもいいなと思いました。一方、ワークツリーの管理は拡張機能などを吟味して使わないと、複雑になりそうです。まだまだ使い始めたばかりで慣れてないのですが、しばらくは使ってみようと思ってます。