いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

git worktreeで複数ブランチを並行させて作業する

概要

便利なのだけど、いざ使う時に忘れていることが多いので、ここにメモメモ。

git には、git worktree という便利なサブコマンドがあります。

ソースコードを修正しながら対応するドキュメントをちょっと修正したりしたいときがあるけど、でもソースコードを修正するブランチとドキュメントを調整するブランチは別ってときがたまにあります。

ソースコードのコミットは、ブランチAで。

ドキュメントのコミットは、ブランチBでやりたいって感じですね。

また、新しい機能を作ってるんだけど、ブランチXのコードを参考にしながら作業したいときとか。

また、実務だと「前リリース時点から変更されたやつのdiff結果をWinMergeとかつかってレポートでほしい」とか。

そういうとき、最初素直に頭に浮かぶのは

  • もう一個クローンして、そこを望みのブランチでcheckoutしておいて作業する

とかになると思います。私も昔そうでしたw

で、こういうときに git には git worktree という機能があって、楽に複数ブランチで並行作業しやすいようになっています。

基本、利用するのは以下のコマンド

  • git worktree add ディレクトリパス [-b] ブランチ名orコミットハッシュ
  • git worktree remove ディレクトリパス

addすると指定のディレクトリパスに指定したブランチの内容がコピーされます。

このコピーされたディレクトリは、元のgit管理下のディレクトリに紐付いているので、コミットとかすると、ちゃんと元のリポジトリでも反映されるようになっています。

git worktree removeすると、一時作業用に worktree を作っていた場所を消してくれます。そのworktreeで入れたコミットとかは、ちゃんと残ります。

試してみる

とりあえず、試してみます。

まず、作業場所作る

$ mkdir git-worktree-example
$ cd git-worktree-example
$ git init
$ git switch -c master

適当にコミットを積む

$ cat - << EOF > main.go
package main

import "fmt"

func main() {
    fmt.Println("hello go")
}
EOF
$ git add main.go
$ git commit -m "Add main.go"

ドキュメント作成用のブランチつくって、README.md用意

$ git switch -c document
$ cat - << EOF > README.md
# git worktree example
EOF
$ git add .
$ git commit -m "Add README.md"

これで2つのブランチが現在存在することになりますね。

$ git switch master
$ git branch
  document
* master

ここから、コード作りながらドキュメントも並行して更新していこうとおもいます。

で、ここで git worktree が使えます。ドキュメント用のブランチを別のツリーとして用意。

$ git worktree add ../document document
Preparing worktree (checking out 'document')
HEAD is now at c89a05f Add README.md

これで、../document の場所に document ブランチが worktree としてコピーされました。

別のターミナル開いて、移動してみるとよくわかります。

$ cd ..
$ ls
document git-worktree-example
$ cd document
$ git branch
* document
+ master

worktree 状態のものは、ブランチ表示のときに + が表示されます。

$ git worktree list
/home/gitpod/tmp/git-worktree-example  41cc600 [master]
/home/gitpod/tmp/document              c89a05f [document]

これで、2つのブランチを並行で使えます。

まず、masterブランチでコードを作る

$ git branch
+ document
* master
$ go mod init gitworktreeexample
go: creating new go.mod: module gitworktreeexample
$ git add .
$ git commit -m "Add go.mod"

もう片方のターミナルに切り替えて、ドキュメント調整

$ git branch
* document
+ master
$ echo 'This is example that git worktree command' >> README.md
$ git add .
$ git commit -m "Update README.md"

これで両方のブランチでコミットが追加されています。worktree同士のコミットはちゃんとリンクされているので

master側でもdocument側でも比較すると差異が見れます。(普通にブランチにコミット追加しているのと同じです。)

# masterブランチ側
$ git log --no-merges document..master --oneline
4e888f4 (HEAD -> master) Add go.mod

# document ブランチ側
$ git log --no-merges master..document --oneline
bd131ee (HEAD -> document) Update README.md
c89a05f Add README.md

てことで、documentブランチをmasterにマージ

$ git merge document
Merge made by the 'recursive' strategy.
 README.md | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 README.md

$ ls
README.md  go.mod  main.go

$ git log --oneline
0557454 (HEAD -> master) Merge branch 'document'
bd131ee (document) Update README.md
4e888f4 Add go.mod
c89a05f Add README.md
41cc600 Add main.go

オケですね。てことで、worktree としてコピーしていた document ブランチの場所はいらなくなったので後片付け。

$ git worktree remove document
$ cd ..
$ ls
git-worktree-example

worktree としてコピーされていたディレクトリが消えてますね。

補足)worktreeが間違って削除されないようにする

worktree としてコピーした場所をgit操作間違って削除されないようにするには

以下のようにします。

$ git worktree lock document

これで、このworktreeの場所はロックされましたので、git worktree remove してもエラーになります。

ロック解除する場合は以下のようにします。

$ git worktree unlock document

これで削除できるようになります。


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com