Hatena::ブログ(Diary)

www.kotha.netの裏

2017-12-29

マージコンフリクトを高速に解決するためのvimプラグイン

| 23:44

多くの場合、gitのマージコンフリクト解決は簡単な作業である。これに生活の約1%を費やしているのだが、どうも適切な道具がないせいで効率が悪い気がしていた。たとえばvimdiffは、二つのファイルを比較するのであればとても優秀なのだが、マージコンフリクトを素朴に扱うと4つものバージョン(マージしようとしている2つと、その共通祖先、そして現在のファイル)を比較することになり、いたるところがハイライトされて訳が分からなくなったり、行の対応が崩れるといった問題が高頻度で発生する。また、gitのマージ処理は行単位であるが、単語単位や文字単位で差分を見れば実は自動で解消しうるコンフリクトも少なくない。こういうものは自動で処理してほしい。

こういうことをするvimプラグインは当然すでに存在するだろうと思って探したのだが、見つからなかったので自分で書いた

こんな感じで使う。

デモ画像

見て分かるように、特にペインを分けたりせず、コンフリクトマーカのあるファイル上で直接作業する。ただし重要な点が一つあって、マージしようとしている二つのバージョン(以下A、B)だけでなく、その共通祖先(正確にはmerge-base)もコンフリクトに含まれるようにしておく必要がある(git config --global merge.conflictStyle diff3)。これがないと、どんな変更をマージしようとしているか分からないので、このプラグインには何もできない*1

:Conflict3Highlightコマンドで、diffによる色付けをしている。上のデモでは、Aと共通祖先を比較してその差分を青で塗り、次にBと共通祖先を比較してその差分を水色で塗っている。

この色分けを頼りに手動でコンフリクトを解消しても良いし、このデモのように、プラグインが提供するコマンドを使って半自動で解消しても良い。

半自動での解消は、コンフリクトを段階的に単純化・縮小していくことによって行なう。最終的にコンフリクトが空になった時が解消完了である。

:Conflict3ResolveOneコマンドは、コンフリクトから自動解消できる部分を探して、三つのバージョン(A、B、祖先)でその部分が同じになるようにコンフリクト単純化する。:Conflict3ResolveAllコマンドは、これを可能な限り繰り返す。:Conflict3Shrink!コマンドは、既に解消されてバージョン間の差がなくなった行をコンフリクトの外に出す。これによって空になったコンフリクトは消される。

実装

実装について特筆すべきことは、diffアルゴリズムvim scriptで自前実装している(行単位はA*、文字単位はMyers法)ことくらいだろうか。これによって、diffアルゴリズムの微妙なカスタマイズが可能になっている。例えば、文字単位diffでは、細かいhunkが増えるのを防ぐために、hunk一個ごとにペナルティを課している。

しかし実装作業としては、diffアルゴリズムよりも、ハイライトや編集作業のこまごまごした処理のために大量の場合分けを書く方が苦しかった。

今のところコードの質はかなりひどくて、クソの山と言っても良いと思う。これはいずれ改善する。

結論

ハイライト部分だけは数ヶ月前に完成していたので、ずっと実用していたのだが、既に手放せなくなっている。とても便利なプラグインになったと思うので、gitを扱うvim使いにはぜひ試してほしい。

vimプラグインを書くのは初めてなので、なにか変なことをしていたら教えていただけるとありがたい。

*1:このプラグインを使わない場合でもこれを指定しない理由はないように思う

トラックバック - http://d.hatena.ne.jp/mkotha/20171229/1514558680