理系学生日記

おまえはいつまで学生気分なのか

具象構文木でdiffが取れるdifftastic

ソースファイルをdiffで比較する

ファイルやディレクトリの差分を確認するコマンドは、diffです。 diffは一般に文字列で比較するため、プログラミング言語のソースファイルのdiffをとる場合は、何が変わったかわからないことが多くありました。

たとえば次に示すdiffの差分では、Javaのソースファイルに対して以下2つの変更を検出しています。

  1. パラメータに対するfinal修飾子の追加
  2. System.out.printlnの引数の改行

前者はコードの意味を変更するのに対して、後者は単に見た目の変更です。 しかし、diffの結果を見る限りでは、どちらも同じように扱われています。

diff -u Main.orig.java Main.java
--- Main.orig.java    2023-12-30 01:27:43
+++ Main.java 2023-12-30 01:29:11
@@ -4,7 +4,7 @@

 public class Main {
     // NFDでの正規化を行い、正規化後の文字列を返す
-    public static String NFD(String str) {
+    public static String NFD(final String str) {
         return Normalizer.normalize(str, Normalizer.Form.NFD);
     }

@@ -37,7 +37,8 @@
         System.out.println("|文字列|Unicode|NFD|NFD Unicode|NFC|NFC Unicode|NFKD|NFKD Unicode|NFKC|NFKC Unicode|");
         System.out.println("|---|---|---|---|---|---|---|---|---|---|");
         for (String str : strs) {
-            System.out.println("|" + str + "|" + getUnicode(str) + "|"
+            System.out.println("|" + str
+                    + "|" + getUnicode(str) + "|"
                     + NFD(str) + "|" + getUnicode(NFD(str)) + "|"
                     + NFC(str) + "|" + getUnicode(NFC(str)) + "|"
                     + NFKD(str) + "|" + getUnicode(NFKD(str)) + "|"```

ソースファイルを具象構文木で比較するdifftastic

ソースファイルから具象構文木を生成し、その構文木を比較することで、プログラミング言語のソースファイルのdiffをとることができるツールとしてdifftasticがあります。

まずは使ってみましょう。先のdiffと同様のファイルを比較してみた結果が以下です。 finalの追加に対しては追加した箇所にハイライトがされている一方、単に改行のみを変更した箇所は差分として検出されていません。これがdiffstasticの特徴です。

difftasticはソースファイルを具象構文木に変換し、その構文木を比較することで、ソースファイルのdiffをとります。抽象構文木(AST)というのはよく聞きますが、difftasticが利用する具象構文木(CST)は空白やコメント、括弧等のASTでは省略される構文的要素を含む構文木です。difftasticはこのCSTを利用して、構文的な差分を検出してくれるわけです。

差分の見せ方

上記の差分のキャプチャは1カラムで出力されていますが、これは単純追加や削除に対し、difftasticがデフォルトで1カラムのみ出力するためです。やや複雑な差分の場合は2カラムで出力されます。 もちろん、強制的に2カラムで出力可能です。このモードの切り替えは--displayオプションで行います。

Gitでの利用法

差分を見るのが頻出するのはgitを利用するタイミングです。

Gitで差分を見る場合はgit diffを使うことが多いですが、外部ツールを使って差分を見る場合は、一般にgit difftoolを使います。

git difftool is a Git command that allows you to compare and edit files between revisions using common diff tools.

Git - git-difftool Documentation

.gitconfigで次のように定義すれば良いでしょう。

git diff .gitconfig
diff --git a/.gitconfig b/.gitconfig
index ca27d5e..54727c7 100644
--- a/.gitconfig
+++ b/.gitconfig
@@ -56,14 +56,25 @@ path = ~/.config/git/tis
 graph = log --graph --branches --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(green)- %an, %cr%Creset'

 [diff]
+tool = difftastic
 # Choose a diff algorithm.
 # patience:
 #   Use "patience diff" algorithm when generating patches.
 algorithm = patience

+[difftool]
+# difftool を使う時に、プロンプトを出すかどうか
+prompt = false
+
+[difftool "difftastic"]
+cmd = difft "$LOCAL" "$REMOTE"
+
+[pager]
+difftool = true
+

そうすると、git difftoolでdifftasticが起動します。

対応言語

difftasticが対応している言語はLanguages Supported - Difftastic Manualに一覧があります。 目ぼしい言語には対応していると言えそうです。