Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
0
Help us understand the problem. What is going on with this article?
@mh_mobiler

git worktreeの操作を便利にする

More than 1 year has passed since last update.

はじめに

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/fugafugafeature/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 + @でコマンドを起動し、移動先のブランチのパスを選択します。
EscCtr+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で削除対象のパスを選択します。
EscCtr+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を活用する上での参考になれば幸いです。

参考サイト

0
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
mh_mobiler
主にモバイルエンジニアです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
0
Help us understand the problem. What is going on with this article?