はじめに
git worktree
コマンドはブランチを並行運用する上でかなり便利なコマンドです。
(ただし、ワーキングツリー上にブランチを切ることでライブラリをクリーンインストールする必要があるため、中には嫌いな人もいるようですね^^)
仕事でも結構使っているのですが、毎回シェルの履歴を探して、git worktree add
のコマンドラインを書き換えて、ブランチの作成を行ったり、git worktree list
でブランチの一覧を取得して、cd
コマンドでブランチのディレクトリに移動するなどの面倒なことをしていることに今更気付きました^^
そこで、ブランチの作成や削除、ブランチの移動を手軽にできないかと思い、シェルスクリプトやpecoを使って、git worktreeの操作を便利にする方法について、あれこれ調べた内容を記載したいと思います。
※ そもそも、git worktree
とは何?という方は、ブランチの切り替えをしなくてよくなるコマンド git worktree がすごい!を参考にしてください。
前提
- zsh
- 本記事に記載のシェルスクリプトはzshを使用します。bashで使用するには、いくつか変更が必要かと思います。
- macOS
- ここでは、macOSに限定します。
- Catalina 10.15で動作検証しています。
- peco
- 移動したいブランチのパスや削除したいブランチのパスを選択するために利用します。
- Git
-
git worktree
コマンドを使用します。 - Git 2.7.0以上を使用します。
-
- シェルスクリプト
-
git worktree
コマンドを使ったシェルスクリプトを作成します。 - 本記事に記載のシェルスクリプトの関数の引数はバリデーションを行っていないため、実際に使用する場合はご注意ください。
-
インストール
未インストールの場合は、必要に応じてインストールしてください。
これらは、HomeBrewを使ってもインストールできます。
git
brew install git
zsh
brew install zsh
peco
brew install peco
シェルスクリプト
ここでは、git worktreeを操作するシェルスクリプトの内容について説明します。
ブランチのワーキングツリー用の親ディレクトリパスの指定
まずは、作成するワーキングツリーのブランチを格納するための親ディレクトリを指定します。
ここは、任意のディレクトリを指定します。
ここで指定した親ディレクトリを基準に、ブランチの作成・削除・移動などを行います。
# ワーキングツリー用の親ディレクトリパス
GIT_WORKTREE_DIR="${HOME}/.git-worktrees/"
ワーキングツリーにブランチの作成
指定の親ディレクトリパスの配下にブランチ用のディレクトリを作成するシェルスクリプトの関数です。
ディレクトリの管理をしやすくするために、親ディレクトリ配下に、<リポジトリ名>/<ブランチ名> の命名規則で作成します。
例えば、リポジトリ名が MyRepo
で、ブランチ名に feature/hogehoge
を指定した場合は、
${HOME}/.git-worktrees/MyRepo/feature/hogehoge
のパスにブランチのディレクトリが作成されます。
# ワーキングツリーにブランチを作成する
#
# [使い方]
# $ gwt <リポジトリ名> <ブランチ名>
#
# [例]
# $ gwt MyRepo feature/hogehoge
function gwt() {
git worktree add ${GIT_WORKTREE_DIR}$1/$2 -b $2
}
ワーキングツリーのブランチのディレクトリに移動する
ブランチのディレクトリに移動する関数です。
git worktree list
を呼ぶことでブランチ一覧を取得できるため、これらの一覧をpecoの標準入力に渡すことで、移動するブランチの絞り込みを行います。また、pecoでブランチのパスを選択後に cdコマンドで選択したパスのディレクトリに移動します。
この関数は直接呼び出さずに、ターミナル上で Ctrl+@
を押下することで呼び出しを行います。
# pecoで選択したブランチのディレクトリに移動する
#
# [使い方]
# Ctrl + @で起動し、移動するブランチのパスを選択する。
function peco-gwt-cd () {
local selected_dir=$(git worktree list | awk '{ print $1}' | peco --query "$LBUFFER")
if [ -n "$selected_dir" ]; then
BUFFER="cd ${selected_dir}"
zle accept-line
fi
}
zle -N peco-gwt-cd
bindkey '^@' peco-gwt-cd
ワーキングツリーのブランチを削除する
ワーキングツリー上のブランチのディレクトリを削除する関数です。
ディレクトリの移動方法と同様に、git worktree list
を呼ぶことでブランチ一覧を取得できるため、これらの一覧をpecoの標準入力に渡すことで、削除するブランチの絞り込みを行います。また、pecoでブランチのパスを選択後に gwt-rm関数を呼び出しディレクトリを削除します。
gwt-rm関数では、指定のディレクトリを削除してよいか確認するプロンプトを表示し、y
あるいは Y
が入力された場合に削除を行います。
この関数は直接呼び出さずに、ターミナル上で Ctrl+r
を押下することで呼び出しを行います。
# pecoで選択したブランチを削除する。
#
# [使い方]
# Ctrl + rで起動し、pecoで削除対象のパスを選択する。
# 削除しても良い場合は、y or Yを選択する。
function peco-gwt-rm () {
local selected_dir=$(git worktree list | awk '{ print $1}' | peco --query "$LBUFFER")
if [ -n "$selected_dir" ]; then
BUFFER="gwt-rm ${selected_dir}"
zle accept-line
fi
}
zle -N peco-gwt-rm
bindkey '^r' peco-gwt-rm
# 指定のワーキングツリー上のブランチのディレクトリを削除する。
#
# [使い方]
# $ gwt-rm <ワーキングツリー上のブランチのパス>
function gwt-rm() {
echo "$1を削除してもよろしいですか?(y/N): "
if read -q; then
# 強制的にワークツリー上のブランチを削除する。
git worktree remove -f $1
# rm -rf $1
# git worktree prune
else
echo abort
fi
}
ワーキングツリーのブランチのディレクトリを別のパスに移動する。
ワーキングツリー上に作成したブランチのディレクトリを別のパスに移動する関数です。
この関数はそこまで使用することはないと思いますが、内部的にgit worktree move
を使って、移動元のブランチのディレクトリを移動先のディレクトリに移動させます。ただし、移動先は移動元と同じリポジトリ配下に限定しています。
# ワーキングツリーのディレクトリを別のパスに移動する
# ただし、ここでは、指定する移動元と移動先は同一のリポジトリ配下のパスに限定する。
#
# [使い方]
# $ gwt-mv <リポジトリ名> <移動元のブランチ名> <移動先のブランチ名>
#
# [例]
# $ gwt-mv MyRepo feature/hogehoge feature/hogefuga
#
# [備考]
# ・移動先のパスのディレクトリがない場合はgit worktree move が失敗するため、事前にディレクトを作成する。
# ・移動元のパスの親ディレクトはgit worktree move実行後も残ったままになることを注意する。
# ・mainのワーキングツリーの移動には対応していない。
function gwt-mv() {
mkdir -p ${GIT_WORKTREE_DIR}$1/`dirname $3`
git worktree move ${GIT_WORKTREE_DIR}$1/$2 ${GIT_WORKTREE_DIR}$1/$3
}
.zshrcに記載のシェルスクリプト全文
上記で記載したシェルスクリプトの全文です。
使用する場合は、.zshrcに記載してください。
記載後は、シェルを再起動するか sourceコマンドなどで .zshrcの再読み込みを行ってください。
# ワーキングツリー用のディレクトリパス
GIT_WORKTREE_DIR="${HOME}/.git-worktrees/"
# ワーキングツリーにブランチを作成する
#
# [使い方]
# $ gwt <リポジトリ> <ブランチ名>>
#
# [例]
# $ gwt MyRepo feature/hogehoge
function gwt() {
git worktree add ${GIT_WORKTREE_DIR}$1/$2 -b $2
}
# pecoで選択したブランチのディレクトリに移動する
#
# [使い方]
# Ctrl + @で起動し、移動するブランチのパスを選択する。
function peco-gwt-cd () {
local selected_dir=$(git worktree list | awk '{ print $1}' | peco --query "$LBUFFER")
if [ -n "$selected_dir" ]; then
BUFFER="cd ${selected_dir}"
zle accept-line
fi
}
zle -N peco-gwt-cd
bindkey '^@' peco-gwt-cd
# pecoで選択したブランチを削除する。
#
# [使い方]
# Ctrl + rで起動し、pecoで削除対象のパスを選択する。
# 削除しても良い場合は、y or Yを選択する。
function peco-gwt-rm () {
local selected_dir=$(git worktree list | awk '{ print $1}' | peco --query "$LBUFFER")
if [ -n "$selected_dir" ]; then
BUFFER="gwt-rm ${selected_dir}"
zle accept-line
fi
}
zle -N peco-gwt-rm
bindkey '^r' peco-gwt-rm
# 指定のワーキングツリー上のブランチのディレクトリを削除する。
#
# [使い方]
# $ gwt-rm <ワーキングツリー上のブランチのパス>
function gwt-rm() {
echo "$1を削除してもよろしいですか?(y/N): "
if read -q; then
# 強制的にワークツリー上のブランチを削除する。
git worktree remove -f $1
# rm -rf $1
# git worktree prune
else
echo abort
fi
}
# ワーキングツリーのディレクトリを別のパスに移動する
# ただし、ここでは、指定する移動元と移動先は同一のリポジトリ配下のパスに限定する。
#
# [使い方]
# $ gwt-mv <リポジトリ> <移動元のブランチ名> <移動先のブランチ名>
#
# [例]
# $ gwt-mv MyRepo feature/hogehoge feature/hogefuga
#
# [備考]
# ・移動先のパスのディレクトリがない場合はgit worktree move が失敗するため、事前にディレクトを作成する。
# ・移動元のパスの親ディレクトはgit worktree move実行後も残ったままになることを注意する。
# ・mainのワーキングツリーの移動には対応していない。
function gwt-mv() {
mkdir -p ${GIT_WORKTREE_DIR}$1/`dirname $3`
git worktree move ${GIT_WORKTREE_DIR}$1/$2 ${GIT_WORKTREE_DIR}$1/$3
}
使い方
ワーキングツリーにブランチの作成
gwtコマンドでブランチの作成を行います。
コマンドの形式は次の通りです。
$ gwt <リポジトリ名> <ブランチ名>
例えば、${HOME}/work/repos
上に flutterのリポジトリを cloneをしてきて試してみます。
※ あらかじめ、${HOME}/work/repos
のディレクトリを作成しておきます。
$ mkdir -p ${HOME}/work/repos
$ cd ${HOME}/work/repos
$ git clone git@github.com:flutter/flutter.git
flutterディレクトリに移動後に、Ctrl + @
を押下してみます。
$ cd flutter
Ctrl + @
を押下すると、mainのワーキングツリーのみが表示されます。
QUERY>
/Users/<ユーザー名>/work/repos/flutter
このブランチを表示すると、現在のブランチがmaster
ブランチであることが分かります。
※ この時点でmasterブランチのみが表示されます。
$ git branch
* master
つぎに、gwt
コマンドの引数のリポジトリ名にflutter
、ブランチ名にfeature/fugafuga
を指定し、ブランチを作成します。
$ gwt flutter feature/fugafuga
上記コマンド実行結果は次のようになります。
$ gwt flutter feature/fugafuga
Preparing worktree (new branch 'feature/fugafuga')
HEAD is now at 2cbf126fe Add `brightness` to CupertinoNavigationBar (fixes #46216) (#47521)
ここで、Ctrl + @
を押下すると、作成したfeature/fugafuga
のディレクトリのパスが表示されます。
QUERY>
/Users/<ユーザー名>/work/repos/flutter
/Users/<ユーザー名>/.git-worktrees/flutter/feature/fugafuga
同様に、もう一つブランチを作成してみます。
リポジトリ名にflutter
、ブランチ名にfeature/hogehoge
を指定し、ブランチを作成します。
$ gwt flutter feature/hogehoge
上記コマンド実行結果は次のようになります。
$ gwt flutter feature/hogehoge
Preparing worktree (new branch 'feature/hogehoge')
HEAD is now at 2cbf126fe Add `brightness` to CupertinoNavigationBar (fixes #46216) (#47521)
ここでまた、Ctrl + @
を押下すると、作成したfeature/hogehoge
のディレクトリのパスが表示されます。
QUERY>
/Users/<ユーザー名>/work/repos/flutter
/Users/<ユーザー名>/.git-worktrees/flutter/feature/fugafuga
/Users/<ユーザー名>/.git-worktrees/flutter/feature/hogehoge
実際に $HOME/.git-worktrees/flutter
に移動して、tree表示してみると、
feature/fugafuga
とfeature/hogehoge
が作成されていることを確認できます。
$ cd $HOME/.git-worktrees/flutter
$ tree -L 3
.
└── feature
├── fugafuga
│ ├── AUTHORS
│ ├── CODEOWNERS
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── LICENSE
│ ├── PATENT_GRANT
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── bin
│ ├── dartdoc_options.yaml
│ ├── dev
│ ├── examples
│ ├── flutter_console.bat
│ └── packages
└── hogehoge
├── AUTHORS
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── PATENT_GRANT
├── README.md
├── analysis_options.yaml
├── bin
├── dartdoc_options.yaml
├── dev
├── examples
├── flutter_console.bat
└── packages
12 directories, 20 files
次に、$HOME/.git-worktrees/flutter/feature/fugafuga
に移動し、ブランチを表示すると、feature/fugafuga
にブランチが切り替わったことが分かります。
$ cd $HOME/.git-worktrees/flutter/feature/fugafuga
$ git branch
* feature/fugafuga
+ feature/hogehoge
+ master
同様に、$HOME/.git-worktrees/flutter/feature/hogehoge
に移動し、ブランチを表示すると、feature/hogehoge
にブランチが切り替わったことが分かります。
$ cd $HOME/.git-worktrees/flutter/feature/hogehoge
$ git branch
+ feature/fugafuga
* feature/hogehoge
+ master
ワーキングツリーのブランチのディレクトリに移動する
任意のワーキングツリーのブランチに移動するために、Ctrl + @
でコマンドを起動し、移動先のブランチのパスを選択します。
※ Esc
やCtr+C
を押下することで処理をキャンセルできます。
現在、 $HOME/.git-worktrees/flutter/feature/hogehoge
のディレクトリにいるとします。
この状態で、 Ctrl+@
を押下すると、次のようにワーキングリストの一覧が表示されます。
QUERY>
/Users/<ユーザー名>/work/repos/flutter
/Users/<ユーザー名>/.git-worktrees/flutter/feature/fugafuga
/Users/<ユーザー名>/.git-worktrees/flutter/feature/hogehoge
この時、/Users/<ユーザー名>/work/repos/flutter
にキーボードで移動 もしくは、repos/flutter
のようにキーボードを入力して絞り込み、/Users/<ユーザー名>/work/repos/flutter
を選択します。
パスを選択後、pwd
で現在のパスを表示してみます。
$ pwd
/Users/<ユーザー名>/work/repos/flutter
ブランチを表示した場合は、materブランチにブランチが切り替わったことが分かります。
$ git branch
+ feature/fugafuga
+ feature/hogehoge
* master
ワーキングツリーのブランチを削除する
任意のワーキングツリー上のブランチを削除するために、Ctrl + r
で起動し、pecoで削除対象のパスを選択します。
※ Esc
やCtr+C
を押下することで処理をキャンセルできます。
パスを選択後、削除してよいか聞かれるため、削除がOKの場合は、y
あるいは Y
を入力します。
それでは、feature/hogehoge
のブランチを削除してみます。
現在、 masterブランチの$HOME/work/repos/flutter
のディレクトリにいるとします。
※ $HOME/.git-worktrees/flutter/feature/hogehoge
にいる状態では、feature/hogehoge
のブランチを削除できないため、feature/hogehoge
以外のブランチのディレクトリにいる必要があります。
この状態で、 Ctrl+r
を押下すると、次のようにワーキングリストの一覧が表示されます。
QUERY>
/Users/<ユーザー名>/work/repos/flutter
/Users/<ユーザー名>/.git-worktrees/flutter/feature/fugafuga
/Users/<ユーザー名>/.git-worktrees/flutter/feature/hogehoge
この時、ブランチの移動の時と同様にキーボードで移動 もしくは 絞り込みを行い、/Users/<ユーザー名>/.git-worktrees/flutter/feature/hogehoge
を選択します。
パスを選択後、選択したパスを削除しても良いか確認するプロンプトが表示されます。この時に y あるいは Yを入力すると削除、それ以外(例えばn)を入力すると、処理が強制終了します。
yを選択した場合は次の通りです。
gwt-rm /Users/<ユーザー名>/.git-worktrees/flutter/feature/hogehoge
/Users/<ユーザー名>/.git-worktrees/flutter/feature/hogehogeを削除してもよろしいですか?(y/N):
y%
次に、pwd
で現在のパスを表示してみます。
$ pwd
/Users/<ユーザー名>/work/repos/flutter
masterブランチの$HOME/work/repos/flutter
のディレクトリのままであることが分かります。
また、ブランチを表示した場合は、feature/hogehoge
はローカルブランチに残ったままであることを確認できます。
$ git branch
+ feature/fugafuga
feature/hogehoge
* master
Ctrl+@でワーキングツリーの一覧を取得すると、/Users/<ユーザー名>/.git-worktrees/flutter/feature/hogehoge
がリストに表示されなくなり、ワーキングツリーから削除されたことが分かります。
QUERY>
/Users/<ユーザー名>/work/respo/flutter
/Users/<ユーザー名>/.git-worktrees/flutter/feature/fugafuga
ブランチのfeature/hogehoge
のディレクトリが削除されているかを確認するために、
親ディレクトリの$HOME/work/repos/flutter
のディレクトリに移動し、tree表示します。
$ cd $HOME/.git-worktrees/flutter/
$ tree -L 3
.
└── feature
└── fugafuga
├── AUTHORS
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── PATENT_GRANT
├── README.md
├── analysis_options.yaml
├── bin
├── dartdoc_options.yaml
├── dev
├── examples
├── flutter_console.bat
└── packages
6 directories, 10 files
tree表示を確認すると、無事にfeature/hogehoge
のブランチのディレクトリが削除されたことが分かります。
ワーキングツリーのブランチのディレクトリを別のパスに移動する。
gwt-mvコマンドで移動元のブランチのディレクトリ
を移動先のブランチのディレクトリ
に移動します。
コマンドの形式は次の通りです。
$ gwt-mv <リポジトリ名> <移動元のブランチ名> <移動先のブランチ名>
flutterリポジトリのfeature/fugafuga
ブランチをdevelop/fugafuga
に移動してみます。
まずは、現在、masterブランチの$HOME/work/repos/flutter
のディレクトリにいるとします。
また、gwt-mv
コマンドを実行する前に Ctrl+@
を押下し、ワーキングツリーの一覧を確認しておきます。
QUERY>
/Users/<ユーザー名>/work/respo/flutter
/Users/<ユーザー名>/.git-worktrees/flutter/feature/fugafuga
次に、以下のgwt-mv
コマンドを実行します。
$ gwt-mv flutter feature/fugafuga develop/fugafuga
正常に移動した場合は、コンソールに何も表示されません。
次に、Ctrl+@
を押下し、ワーキングツリーの一覧を表示します。
QUERY>
/Users/<ユーザー名>/work/respo/flutter
/Users/<ユーザー名>/.git-worktrees/flutter/develop/fugafuga
ブランチのfeature/fugafuga
のパスがdevelop/fugafuga
に変更されたことが分かります。
また、ブランチのfeature/fugafuga
のディレクトリが移動されているかを確認するために、
親ディレクトリの$HOME/work/repos/flutter
のディレクトリに移動し、tree表示します。
$ tree -L 3
.
├── develop
│ └── fugafuga
│ ├── AUTHORS
│ ├── CODEOWNERS
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── LICENSE
│ ├── PATENT_GRANT
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── bin
│ ├── dartdoc_options.yaml
│ ├── dev
│ ├── examples
│ ├── flutter_console.bat
│ └── packages
└── feature
7 directories, 10 files
tree表示すると、移動先のdevelop/fugafuga
にディレクトリが移動したことが分かります。
また、移動元のfeature/fugafuga
のfeatureディレクトリ自体は削除されずに残ったままになっています。
次に、$HOME/.git-worktrees/flutter/develop/fugafuga
に移動し、ブランチの状態を確認します。
$ cd $HOME/.git-worktrees/flutter/develop/fugafuga
$ git branch
* feature/fugafuga
feature/hogehoge
+ master
ディレクトリの移動後も、ローカルブランチ自体は、feature/fugafuga
のままであることが分かります。ローカルブランチをfeature/fugafuga
からdevelop/fugafuga
に切り替えたい場合は、手動でdevelop/fugafuga
ブランチを切る必要があります。
$ git checkout -b develop/fugafuga
Switched to a new branch 'develop/fugafuga'
$ git branch
* develop/fugafuga
feature/fugafuga
feature/hogehoge
+ master
上記により、ローカルブランチがdevelop/fugafuga
ブランチに切り替わります。
さいごに
作成したシェルスクリプトは荒削りですが、git worktree
を直接操作するよりも、git worktree
の操作を便利にできるようになったように個人的には思います。git worktree
を活用する上での参考になれば幸いです。