LoginSignup
3
3

More than 3 years have passed since last update.

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

Posted at

CLIにおけるgitは奥が深い

以下のQiitaの記事で衝撃を受けました。
git操作はGUIツール派な自分もCUIに乗り換えた便利git拡張まとめ

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

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

git branch | fzf | xargs git checkout???

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

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

そりゃそうよね。

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

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

ブランチが選択できるようになったけど、この時点ではどれを選んでも何も起きない。(ブランチは切り替わらない。)

fzfについては、fzfを使おう等を参照。
ちなみに、私は普段は類似ツールのpecoを使ってますが、ここで紹介されているコマンドはfzfとそのオプションコマンドを使用しているので、pecoとは単純に置き換えできません。

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

 > 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

コマンドにすると以下のようになるようです。

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"

うーん、わからん!!!

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

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 ならば真となる」。
ということで、当コマンドを実行する際に何らかのパラメーターを指定して実行していたら、②が真にならずに⑤の処理が実行されるようです。

③ブランチとそのコミットログを一覧化する

パラメーター未指定時はこちらが実行されます。

$(git branch --all | grep -v HEAD | fzf --preview 'echo {} | cut -c 3- | xargs git log --color=always')

分解してもまだまだ長い。

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

 > 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を実行してみる。

 > 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-は「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を実行しています。

元記事で紹介されている他のコマンドについて

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

3
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3