より低レベルな Git コマンドを使って他のプログラム (e.g. Vim) と連携する

このエントリはGit advent calendar 2013の24日目のエントリです。


私は普段から Vim というテキストエディタを使ってコードを書いています。

また unite.vim というプラグインをファイルやバッファを選択して開くといった用途などに利用しています。unite.vim はもっと汎用的で強力なプラグインですが詳しい説明は他に譲ります。

つまるところ unite.vim は「列挙」した候補を「選択」しなにかのアクションを「実行」するためのプラグインです。

ところで Git を利用した開発ではマージ時のコンフリクトに遭遇することがしばしばあり、たまに大量のファイルが一度にコンフリクトしそれを解決しなければならない、いわゆる「コンフリクト解決地獄」に遭遇することが稀によくあります。

それを解決するために git status などの出力を眺めながらエディタでいちいち開いて解決していては日も暮れてしまいます。

そこで unite.vim の力を借りることにします。

" unite-git-files-conflict {{{
let s:unite_git_files_conflict = {
      \   'name' : 'git/files/conflict',
      \ }
function! s:unite_git_files_conflict.gather_candidates(args, context)
  let output = unite#util#system('git diff-files --name-only --diff-filter=U')
  let candidates = map(split(output, "\n"), '{
        \ "word" : fnamemodify(v:val, ":p"),
        \ "source" : "git/files/conflict",
        \ "kind" : "file",
        \ "action__path" : fnamemodify(v:val, ":p"),
        \ }')
  return candidates
endfunction
call unite#define_source(s:unite_git_files_conflict)
" }}}
dotfiles/.vim/vimrc at 41908c62aa6347ba737cf2e6f1db9b163001eb03 · aereal/dotfiles · GitHub

肝は git diff-files --diff-filter=U です。git-diff-filesgit-diff のような人間が扱うことを想定している高レベルなコマンド *1 より低レベルで機械で処理しやすい出力を備えたコマンド *2 です。

--diff-filter オプションは表示するファイルを状態でフィルタします。たとえば A を引数として渡すと「ステージに上がっている」=「index に登録されている」ファイルのみを表示します。

この値は git status --short の出力と互換があるのでそれを意識すると覚えやすいかもしれません。詳しくは man git-diff-files で。

他にも Git には低レベルなコマンド群が存在し、一部の人間むけのコマンドはそれらを組み合わせたものでもあります。

低レベルなコマンド群はより機械的に処理しやすいフォーマットの出力を備えていることが多いので、それらを利用して外部のプログラムと連携させるのもよいと思います。

*1:Main porcelain command と呼ばれています

*2:Interrogation commands と呼ばれています