Shogo's Blog

Mar 24, 2015 - 1 minute read - git

git diffでcsvの差分を見やすく表示する

ExcelやGoogle Spreadsheetを使って作ったデータをプログラムに取り込むのにcsv形式が便利でよく使っているんですが、 gitで履歴管理をしてもdiffが見づらい・・・。 gitのdiffがかなり自由にカスタマイズできることを知ったので、いろいろいじってみたメモ。

例として、以下のようなcsvファイルを編集することを考えます。

id,name,param_a,param_b,param_c,param_d,param_e
101,hoge,314,159,265,358,979
102,fuga,271,828,182,845,904

一行目は列の見出しになっていて、プログラムからは列番号ではなくparam_dの様に指定する、 という作りになってます。 id: 101の行のparam_dの数値に変更が入った場合、普通のgitだと以下のようになります。

diff --git a/hogehoge.csv b/hogehoge.csv
index c8dbd17..37f4ff5 100644
--- a/hogehoge.csv
+++ b/hogehoge.csv
@@ -1,3 +1,3 @@
id,name,param_a,param_b,param_c,param_d,param_e
-101,hoge,314,159,265,358,979
+101,hoge,314,159,265,359,979
 102,fuga,271,828,182,845,904

二行目に何か変更があったことはわかりますが、 param_d だとはすぐにはわかりませんね・・・

YAMLに変換して比較する

バイナリファイルであっても差分が確認できるよう、 git-diffを実行する前に変換ツールを実行する機能があります。 拡張子がcsvのファイルに対してこの機能が働くように.gitattributesに以下の行を足します。

*.csv diff=csv

.git/config に変換ツールの設定を追加します。 key: valueの形式になっていると見やすそうなので、変換先の形式にはyamlを選びました。

[diff "csv"]
    textconv = csv2yaml

ここで指定しているcsv2yamlは自前で用意する必要があります。 インターネット上をさまよえば同名のツールはいくらでもありそうですが、今回は自分でgoを使って書きました。 csv2yaml.goをコンパイルしてパスの通る場所においておきましょう。 csv2yamlは自分のよく使うcsvのフォーマットにあわせて以下のようなカスタマイズをしてあります。

  • idという名前のキーを必ず最初にする
  • それ以外のキーはアルファベット順にソートする

この状態でgit diffを実行すると以下のようになります。

diff --git a/hogehoge.csv b/hogehoge.csv
index c8dbd17..37f4ff5 100644
--- a/hogehoge.csv
+++ b/hogehoge.csv
@@ -3,7 +3,7 @@
   param_a: "314"
   param_b: "159"
   param_c: "265"
-  param_d: "358"
+  param_d: "359"
   param_e: "979"
 - id: "102"
   name: fuga

これなら param_d が変更されたとすぐに分かりますね。

hunk-headerを設定する

めでたくparam_dが変更されていることがわかるようになったのですが、 今度はどの行が変更されたのかがわからなくなってしまいました。

差分の @@ -3,7 +3,7 @@ となっている部分はhunk-headerといって、自由にカスタマイズすることができます。 .git/config に表示したい文字列のパターンを入力しましょう。

[diff "csv"]
    textconv = csv2yaml
    xfuncname = "^- .*$"

この状態で差分を確認すると「id: 101param_dをいうパラメータ」が更新されたことが一目瞭然ですね!

diff --git a/hogehoge.csv b/hogehoge.csv
index c8dbd17..37f4ff5 100644
--- a/hogehoge.csv
+++ b/hogehoge.csv
@@ -3,7 +3,7 @@ - id: "101"
   param_a: "314"
   param_b: "159"
   param_c: "265"
-  param_d: "358"
+  param_d: "359"
   param_e: "979"
 - id: "102"
   name: fuga

csv2yamlを作るときに「idという名前のキーを必ず最初にする」としたのはこの機能を使うためです。 僕のユースケースではidがわかれば十分なことがほとんどですが、 場合によってはもっと別の情報の方がいいかもしれませんね。

まだ設定したばっかりなので本当に有用かはよくわかってないですが、しばらくこの設定で試してみようと思います。

参考