Gitとfzfとシェルを組み合わせてGitを便利に使うときに、そこで何が起きているのかを紐解いてみる

# CLIにおけるgitは奥が深い
以下のQiitaの記事で衝撃を受けました。
[git操作はGUIツール派な自分もCUIに乗り換えた便利git拡張まとめ](https://qiita.com/yukiarrr/items/9c21d97f6c8ac31de157)

めっちゃ便利！
とりあえずコピペで自分の環境に取り込んでみたものの、git力も無ければシェル力も無いためここで紹介されているコマンドで何が起きているのかサッパリわからん・・・

というわけで、紹介されているコマンドを題材にどのようなことが起きているのかを紐解いてみました。

# git branch | fzf | xargs git checkout？？？

まずは`git branch`だけを実行してみる。

``` bash
 > git branch                                                                                                                                                                                                        (git)-[CreateSchedule#2]
  CreateFrontendEnv#7
* CreateSchedule#2
  master
```

そりゃそうよね。

次は`git branch | fzf`を実行してみる。
これは、`git branch`の内容をfzfに渡しているだけ。


``` bash
>
  3/3
>   CreateFrontendEnv#7
  * CreateSchedule#2
    master
```

ブランチが選択できるようになったけど、この時点ではどれを選んでも何も起きない。(ブランチは切り替わらない。)
 >fzfについては、[fzfを使おう](https://qiita.com/kompiro/items/a09c0b44e7c741724c80)等を参照。
ちなみに、私は普段は類似ツールのpecoを使ってますが、ここで紹介されているコマンドはfzfとそのオプションコマンドを使用しているので、pecoとは単純に置き換えできません。


最終的に`xargs`で始まる部分を追加したらブランチが切り替わるようになるわけだけど、何が起きているのでしょうか？
`xargs`コマンドに-pオプションをつければ、生成されるコマンドが見えるようになるらしい。

``` bash
 > git branch | fzf | xargs -p git checkout                                                                                                                                                                         (git)-[CreateSchedule#2]
git checkout master ?...y
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
```

なるほど。fzfで選択したブランチが`xargs`で指定されるコマンドに渡されて普通にgitのコマンドのようになって実行されているのがわかりました。

それでは、ここからが本番。

# リモート含めたbranch一覧から一つ選んでcheckout
コマンドにすると以下のようになるようです。

``` bash
co = "!f() { args=$@; if [ -z \"$args\" ]; then branch=$(git branch --all | grep -v HEAD | fzf --preview 'echo {} | cut -c 3- | xargs git log --color=always'); git checkout $(echo $branch | sed 's#remotes/[^/]*/##'); else git checkout $args; fi }; f"
```

うーん、わからん！！！

１行にしてつなげると構成を理解しづらいので改行を入れてみました。

``` bash:coコマンド
!f() { 
  # ①
  args=$@;   

  # ②
  if [ -z \"$args\" ]; then   
    # ③   
    branch=$(git branch --all | grep -v HEAD | fzf --preview 'echo {} | cut -c 3- | xargs git log --color=always');     

    # ④
    git checkout $(echo $branch | sed 's#remotes/[^/]*/##');     
  else 
    # ⑤
    git checkout $args;
  fi    
};

# ⑥
f    
```

### ①引数の格納
`$@`はコマンドに渡された「全ての引数」を表す。これを`args`に格納して②で評価しています。

### ②引数の有無の確認
-zは「string の文字列長が 0 ならば真となる」。
ということで、当コマンドを実行する際に何らかのパラメーターを指定して実行していたら、②が真にならずに⑤の処理が実行されるようです。

### ③ブランチとそのコミットログを一覧化する
パラメーター未指定時はこちらが実行されます。

``` bash
$(git branch --all | grep -v HEAD | fzf --preview 'echo {} | cut -c 3- | xargs git log --color=always')
```
分解してもまだまだ長い。

まずは`git branch --all`だけで実行してみる。--allを付けることで、リモートブランチも出力対象となります。

``` bash
 > git branch --all                                                                                                                                                                                                  (git)-[CreateSchedule#2]
  CreateFrontendEnv#7
* CreateSchedule#2
  master
  remotes/origin/CreateFrontendEnv#7
  remotes/origin/CreateSchedule#2
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
```

次は`git branch --all | grep -v HEAD `を実行してみる。

``` bash
 > git branch --all | grep -v HEAD                                                                                                                                                                                   (git)-[CreateSchedule#2]
  CreateFrontendEnv#7
* CreateSchedule#2
  master
  remotes/origin/CreateFrontendEnv#7
  remotes/origin/CreateSchedule#2
  remotes/origin/master
```
-vオプションに`HEAD`を指定することで、`remotes/origin/HEAD`を抽出対象外としました。

いよいよ`git branch --all | grep -v HEAD | fzf --preview 'echo {} | cut -c 3- | xargs git log --color=always'`を実行してみる。

これを実行することで、元記事のようにブランチのリストの右にコミットログが表示されるようになります。カーソルでブランチの選択を変えると、それに応じてログの内容も切り替わるのですが、何をしているのでしょうか？

1. まず、`fzf --preview 'echo {}'`を指定することでfzfの結果を`echo`コマンドでプレビューウインドウで表示します。
2. 次に、プレビューウインドウで表示された内容を`cut -c 3-`します。
-cオプションは「切り出す位置のリストを文字数で指定する」というオプションで、3-は「３文字目から行末まで」を示すようです。ただ、私の環境では`cut -c 3-`は指定してもしなくても同じ結果になっているように見えます。。。
3. 最後に、fzfで選択した内容を`xargs git log`に渡してログ内容をプレビュー表示している。`color=always`を付けなければ一切のハイライトの無い状態で出力されますが、これを付けることでコミットログの内容がハイライトされるようになります。

### ④選択したブランチでcheckoutする
③で選択したブランチで`git branch`を実行します。
その際に`sed 's#remotes/[^/]*/##'`で若干の整形をしています。`remotes/origin/master`を選択した場合は、`master`となるようになっています。
>`sed`の基本文法は`sed "s/ABC/DEF/"`のような書き方が一般的ですが、区切り用の文字`/`は`#`などに変更することができます。というか、`/`を使うのが一般的なだけで`/`でないといけないわけではないです。
ディレクトリのように`/`で区切られた文字列を`sed`しようとするとエスケープが大変になりますが、その場合に`#`を使用することでエスケープ地獄から脱出することができます。

### ⑤コマンド実行時にパラメーターを指定している場合の処理
パラメーターを指定した場合はこちらの処理が実行されます。
普通に`git checkout`が実行されるので、`git co master`とすれば、`git checkout master`が実行されます。あらかじめ切り替えたいブランチがわかっている場合でも同じコマンドを使用するための工夫ですね。

### ⑥実行
①〜⑤の内容は関数fとして定義されているので、関数fを実行しています。

# 元記事で紹介されている他のコマンドについて
元記事では他にも５つのコマンドが紹介されていますが、~~ぶっちゃけ面倒になってきたので~~ 上記内容を理解できていれば他のコマンドのことも理解できそうなので以下省略です。
元記事にあるgitlabのリポジトリを覗いてみると記事では紹介されていなかったコマンドもあるようなので、それらも試してみたいと思います。
私自身も便利なコマンドを作れたらQiitaで情報展開したいと思います。