vimdiff使ってますか?差分を取る際には非常に便利ですよね。git difftoolに設定して使っている人も多いと思います。しかしgit diffは差分計算のアルゴリズムを選択できますが、vimdiffはデフォルトでは差分計算のアルゴリズムを選択できません。
git diffの差分アルゴリズムには標準のもの以外にpatienceやhistogramがあり、より人間に読みやすい差分を表示してくれる「賢い」アルゴリズムになっています。それぞれgit diffコマンドのオプション--patienceや--histogramを付けるか、または~/.gitconfig設定ファイルにアルゴリズムを指定することで利用できます。
具体的なアルゴリズムの詳細は僕も詳しくないですが、patienceアルゴリズムは「ファイル内でユニークかつ比較ファイル同士で一致する行をなるべく"変化していない行"と認識する」よう働きます。また、histogramアルゴリズムは「patienceアルゴリズムの基本的ルールを継承しつつ高速化を図ったアルゴリズム」で、多くのファイルでpatienceアルゴリズムと同一の結果が得られるようです。また、histogramアルゴリズムはEclipseに統合されたGitクライアントEGit(が使うJGit)の標準アルゴリズムです。僕はコマンドラインツールのgit diffにおいてもhistogramアルゴリズムを使ってます。
さて、vimdiffにおいてもhistogramアルゴリズムを使いたいところです。vimの設定ではvimdiffの差分計算に使用するコマンドをdiffexprという設定値で指定できます。そして、git diffコマンドも--no-indexオプションを指定することでdiffコマンドと同じようにスタンドアロンツールとして利用できます。じゃあ、diffexprにgit diff --no-indexを指定すればいいのか・・・というと実はそれだけでは失敗します。
理由は、vimがdiffexprに設定したコマンドから受け付ける差分形式が、diffコマンドのnormal形式かed形式でなければならないからです(vimのヘルプにはed形式とだけ書いてあるのですが、normal形式も受け付けます)。そして、git diffコマンドでは直接diffコマンドのnormal形式やed形式で出力することができずunified形式のみに対応しています。
そこで、unified形式をnormal形式に変換するrubyスクリプトを書いて使うことにしました。以下がその変換スクリプトです。利用にはrubyが必要です。
#!/usr/bin/env rubyiwhite=''if(ARGV[0]=='-b')iwhite=ARGV.shift.dup<<' 'enddiffout=`git diff --no-index --no-color -U0 #{iwhite}#{ARGV[0]}#{ARGV[1]}`diffout.sub!(/\A.*?@@/m,'@@')diffout.gsub!(/^\+/,'> ')diffout.gsub!(/^-/,'< ')diffout.gsub!(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@.*/)dobefore_start=$1before_size=$2after_start=$3after_size=$4action='c'if(before_size=='0')action='a'elsif(after_size=='0')action='d'endbefore_end=''if(before_size&&before_size!='0')before_end=",#{before_start.to_i+before_size.to_i-1}"endafter_end=''if(after_size&&after_size!='0')after_end=",#{after_start.to_i+after_size.to_i-1}"end"#{before_start}#{before_end}#{action}#{after_start}#{after_end}"enddiffout.gsub!(/^(<.*)(\r|\n|\r\n)(>)/,'\1\2---\2\3')printdiffout
上記スクリプトを~/bin/git-diff-normal-formatとして保存し、~/binにPATHを通しておきます。そしたら、.vim設定ファイルに次の記述を追加します。
" diffのコマンドsetdiffexpr=MyDiff()function MyDiff()letopt=""if&diffopt=~"iwhite"letopt=opt . "-b "endifsilent execute "!git-diff-normal-format " . opt . v:fname_in . " " . v:fname_new . " > " . v:fname_out
redraw!endfunction
最後に、git diffで使うアルゴリズムを指定します。下記記述ではgit difftoolで起動するコマンドにvimdiffを指定する設定も一緒に行っています。
[diff]
tool = vimdiff
algorithm = histogram
これでvimdiffやgit difftoolを実行した時にhistogramアルゴリズムが使えるようになります(patienceを使いたい場合は、上記設定のalgorithmの値をpatienceに変えてください)。
では具体的な効果を見てみましょう。サンプルとして用意した2つの比較対象のファイルをvimでウィンドウを分割して並べたものが下の画像です。
まずは、今回の設定をする前、デフォルトのアルゴリズムでvimdiffによる差分をとった結果が以下です。
そして、今回の設定をした後の、histogramアルゴリズムでvimdiffによる差分をとった結果が以下です。
こちらのほうが、より差分内容も分かりやすく、かつ実際に手で行った変更の操作に近いものになっていますね。
※ちなみにこのvimdiffの色設定は過去に書いたこの記事を参照。
※自分のブログより転載。(一部をQiita用に修正)