反省はしても後悔はしない

Vim とか備忘録とか。それと関数型言語勉強中

tig なんて目じゃない! Git のログ系 Vim プラグイン gitv & gitv をGit 統合インターフェース化する最強の設定

この記事は Vim Advent Calendar 2012 の 168 日目の記事です。
昨日は id:yonchu さんの accelerated-smooth-scroll という Vimプラグイン を作った (Vim Advent Calendar 2012, 167日目) - よんちゅBlog でした。

はじめに

最近、Git のログを見る系のエントリが多い気がします。今回の Vim Advent Calendar でも

という記事がありましたし、また最近

なんかもありました。
流行ってるのかな???


今回は私が Vim 上で Git のログを見る(+α)ときどうやってるかについて書きます。

gitv!

gitv というプラグインがあります。

上の図のように左側に Git のログ、右側にその時のコミットの diff が表示されます。便利!!
なお、gitv は vim-fugitive に依存しているので vim-fugitive のインストールは必須です。

実は gitv の紹介記事は前書いたことがあるので基本的なことはそちらを参照してください。

vim で gitk 的なことを行う gitv が便利 - 反省はしても後悔はしない。

gitv の使い方応用編

今日の主題はこちらです。
gitv はデフォルトのままでもログを見る分には便利なんですが、それ以外のことはあまり出来ません。*1

そこで、自分の vimrc に gitv を便利に使うための設定を追加します。これにより gitv を単なるログビューアから Git のための統合インターフェースへと昇華させることができます。

下準備

まず準備として vimrc に以下の記述を追加します。

autocmd FileType gitv call s:my_gitv_settings()
function! s:my_gitv_settings()
  " ここに設定を書く
endfunction

*2
gitv を開くと filetype=gitv が設定されるので、これを利用して gitv のバッファだけで使える素敵設定を追加していきます。
以降、設定は上の s:my_gitv_settings() 関数内に書いていきます。

現在のカーソル位置にあるブランチ名を取得してログ上でブランチに checkout する

gitv のログにはブランチ名も表示されますが*3、カーソルをブランチ名に合わせて、そのブランチ名に対して git checkout や reset などの操作ができると便利です。

そのために以下の設定を追加します。

" s:my_gitv_settings 内
setlocal iskeyword+=/,-,.
nnoremap <silent><buffer> C :<C-u>Git checkout <C-r><C-w><CR>

ブランチ名は単なる英数字だけでなく / や - といった記号も使えるので、まずはそれらをキーワードとして含めます。
コマンドモードで <C-r><C-w> を入力するとカーソルの下にあるキーワードを取得できることを利用して、Git のコマンドを組み立てます。
なお、:Git というコマンドは vim-fugitive が提供しているコマンドです。*4
この例だと、ブランチ名の上で C を押すと即座にそのブランチに git checkout することができます。
checkout のところをいろいろ変えればブランチ名を利用する任意のコマンドをすぐに実行することができます。
ちなみに、カーソルを移動させるときは r や R を利用するとブランチの間を簡単に移動できます。

現在のカーソル行の SHA1 ハッシュを取得してログ上であらゆることを実行する

ぶっちゃけ Git はハッシュが全てです。ハッシュの値さえ取得出来れば割となんでも出来ます。いちいち、HEAD からいくつ離れているかを数える必要はありません。

幸い、gitv のウィンドウにはハッシュ値が一番右に表示されています。表示されていれば取得するのは簡単です。

" これは外に定義!
function! s:gitv_get_current_hash()
  return matchstr(getline('.'), '\[\zs.\{7\}\ze\]$')
endfunction

この関数をトップレベルに書いておきます。
あとは、これと expression レジスタ*5を利用して Git のコマンドを組み立てます。

" s:my_gitv_settings 内
nnoremap <buffer> <Space>rb :<C-u>Git rebase <C-r>=GitvGetCurrentHash()<CR><Space>
nnoremap <buffer> <Space>R :<C-u>Git revert <C-r>=GitvGetCurrentHash()<CR><CR>
nnoremap <buffer> <Space>h :<C-u>Git cherry-pick <C-r>=GitvGetCurrentHash()<CR><CR>
nnoremap <buffer> <Space>rh :<C-u>Git reset --hard <C-r>=GitvGetCurrentHash()<CR>

これらの設定により、カーソル位置のコミットに対して rebase, revert, cherry-pick, reset などのコマンドを実行することができます。もちろんそれ以外でもリビジョン*6を指定するコマンドはたいてい可能です。

この設定は非常に強力です。日常の add, commit などは fugitive から(もちろん Vim のマッピングを利用して)実行し、rebase や reset などブランチを使うコマンドを gitv から簡単に行うようにすると、ほぼシェルで git コマンドを叩くことがなくなります。

便利すぎるので、ある日突然の自分の Vim が使えなくなった時に非常にストレスになるので注意しましょう。

ファイルの diff じゃなくて変更されたファイルの一覧が見たい

gitv だけじゃなく tig なんかでもログと一緒にファイルの差分が表示されています。でも時にはファイルの中身よりもどのファイルが変更されたかの方が重要な場合があります。そういう時に、該当のコミットで変更されたファイルの一覧が取得出来れば便利です。

実は、この表示されている diff はファイル名の部分で折りたためるようになっています。すなわち、すべての折りたたみを閉じることでファイル一覧を表示することが可能となります。

この折りたたみを簡単に操作できるように、以下の設定をトップレベルに追加します。

autocmd FileType git setlocal nofoldenable foldlevel=0
function! s:toggle_git_folding()
  if &filetype ==# 'git'
    setlocal foldenable!
  endif
endfunction

そして以下の設定を s:my_gitv_settings 関数内に追加します。

" s:my_gitv_settings 内
nnoremap <silent><buffer> t :<C-u>windo call <SID>toggle_git_folding()<CR>1<C-w>w

これにより、t キーを押すだけですべての diff のウィンドウに対して folding を切り替えます。つまり、ファイルの diff とファイルの一覧を切り替えることができます。
ちなみに、fugitive が nice な foldtext を提供しているのでファイル名だけじゃなくて何行変更があったかも表示されます。*7

まとめ

fugitive と gitv の組み合わせることによって、Git のたいていの作業を Vim 内で完結させることができます。
慣れ過ぎるとシェルで git コマンドを叩くのが苦痛になるので注意。

明日は @s_of_p さんです。

*1:checkout や merge もできますがインターフェースが非常にアレ

*2:例を書いといてなんですが、autocmd には augroup とをつけて vimrc をリローダブルにしましょう

*3:すべてのブランチを表示するには :Gitv --all とします

*4:実は、gitv 上で :Git は上書きされていて fugitive の :Git を呼び出した後、自分自身を更新します

*5:インサート・コマンドモードで<C-r>=を入力することにより Vim の式の結果を挿入できます。:help quote=

*6:ブランチ名とか HEAD^ とか、あとなんだっけ?

*7:foldtext を自分でカスタマイズしてる場合は autocmd FileType git setlocal foldtext=fugitive#foldtext()すると治ります。