3 ポイント 投稿者 GN⁺ 2024-03-22 | 1件のコメント | WhatsAppで共有
  • Difftastic は行単位ではなく、コードの 構文構造 を基準にファイルを比較する CLI diff ツール
  • 内部では tree-sitter でコードをパースし、フォーマット変更で行が変わっても実際に異なる式をより適切に見分けられる
  • コードフォーマッタが項目を複数行に分割したりラッパーが追加された場合でも、変更された構造と維持された内部内容を分けて表示できる
  • Git 設定ガイドとインストール文書が提供されており、複数のプログラミング言語と JSON・YAML のような構造化テキスト形式をサポートする
  • MIT ライセンス で公開されており、ダウンロード、修正、共有が可能で、既存の diff フローに構造的比較を加える用途に適している

行ではなく構文構造を比較する

  • Difftastic はファイルを構文ベースで比較する CLI diff ツール
  • 一般的な行単位 diff よりも、コード構造を理解した結果を示すことに重点を置く
  • コードは tree-sitter でパースする
    • 行中心の diff では変更されたように見える内部式についても、実際の変更有無を見分けられる
  • コードフォーマッタがコードを複数行に分割する場合でも 実際の変更点 を示せる
  • ラッパーが追加されると区切り記号を正確にマッチングする
    • 内部内容も同時に変わっていても 追加されたラッパー を表示できる
  • @@ -5,6 +5,7 @@ のような diff hunk 構文の代わりに、変更前後ファイルの 実際の行番号 を表示する

対応言語と Git 連携

  • プロジェクト資料は GitHubManualInstall で提供されている
  • 対応プログラミング言語の例は次のとおり
    • C++, C#, Clojure, Dart, Erlang, Go, Haskell, Java, JavaScript, Kotlin, Lisp, Lua, OCaml, PHP, Python, R, Ruby, Rust, Scala, TypeScript
    • 全一覧はマニュアルの 対応言語一覧 で確認できる
  • 対応ファイル形式の例は次のとおり
  • Git とあわせて利用でき、マニュアルに Git 設定 の案内がある
  • Difftastic は MIT ライセンス で公開されており、ダウンロード、修正、共有が可能

1件のコメント

 
GN⁺ 2024-03-22
Hacker News のコメント
  • すでにご存じの方もいるかもしれませんが、このツールは tree-sitter(https://tree-sitter.github.io/tree-sitter/) の上に作られており、LSP が解析に対して行ったことを、パースに対して行っているようなものです
    コードを抽象構文木(AST)に変換し、その AST をエディタや diff ツールのようなクライアントが利用できる標準インターフェースを提供します
    こうしたツールは数十の言語を直接サポートする必要がなく、tree-sitter だけをサポートすれば、tree-sitter が対応するすべての言語で自動的に動作できます
    新しい言語を開発するなら tree-sitter パーサを作ればよく、そうすれば tree-sitter を扱うすべてのツールがその言語をサポートできるようになります
    この 2 つの大きな革新のおかげで、この種のツール改善が爆発的に増えています。今やエディタや diff ツールなどは、互いに同じ作業を繰り返さなくても数十〜数百の言語をサポートできるので、本当にすごいことです

    • 完全に同意しますし、昨年書いたコメントを改めて持ち出すなら、tree-sitter が依存関係のない構造である点も強調に値します
      ちなみに私たちのチームメンバーの何人かは OCaml tree-sitter バインディングを保守しており、Semgrep の作業の一環として文法にも頻繁に貢献しています。Semgrep はコード検索と、コード片そのものであるクエリを AST マッチャとしてパースするために tree-sitter を使っています
      リンターを作るときは、対象言語のランタイムも一緒に持ってこなければならないことがよくあります。たとえば Python で組み込みの ast モジュールを使ってパーサを作ると、言語バージョンや機能を合わせる必要があるため、Python 2.7 で実行される Pylint では Python 3 のコードをパースできません
      複数の言語を対象にすると、最初に思うよりずっと面倒になります
      tree-sitter 以前は、最新の構文に追随している保証があるため、言語組み込みの AST ツールを使うのが最善である場合が多くありました
      個人的には、tree-sitter の天才的な点は、従来の文法方式よりも言語パーサを最新状態に保つことをはるかに簡単にしたところにあります
      tree-sitter の設計上の選択についてもっと知りたいなら、Max Brunsfield の Strange Loop での発表を強くおすすめします: https://www.youtube.com/watch?v=Jes3bD6P0To
      その結果、difftastic 以外にも neovim、Zed、Semgrep、GitHub コード検索など、いくつもの新しいツールが tree-sitter の上に作られました
    • tree-sitter が優れたツールであることには同意しますが、実際に文法を書くことは非常に難しい場合があります
      VHDL 用の tree-sitter 文法とハイライト用クエリ一式を書いてみましたが、VHDL の文法を tree-sitter で表現するのは困難が多かったです
    • この説明は正確ではないと思います。tree-sitter を話すというような共通インターフェースはなく、各 tree-sitter パーサは標準的な抽象構文木ではなく、それぞれ異なる具象構文木を出力します
      LSP はクエリ指向コンパイラのための標準インターフェースを使うことで、M 個のエディタと N 個の言語の間に必要になる M×N の統合問題を実際に解決します
      tree-sitter はその問題を解決するというより、エディタやツールで N 個の統合を書く作業をずっと簡単にしてくれるものです
    • tree-sitter で最も気になる点は、関心のあるいくつかの言語ではこのアプローチが通用しないことです
      Common Lisp は完全な Lisp 実装なしにはパースできず、Haskell の文法は十分に複雑なため文法が不完全で、C/C++ はプリプロセッサだけを見ても正確にパースするのが難しく、Perl のパースはチューリング完全です
      他のところで出ていた提案のように、新しいエコシステムでパーサを書き直させるのではなく、既存のパーサが追加出力として生成できる形式を定義するほうがよさそうです
    • 英語のような自然言語用の tree-sitter 文法を作って、各文を 1 つの文単位としてラベル付けできるでしょうか?
      そうすれば difftastic で、見た目上の行ではなく文単位の変更を表示できそうです
      エッセイでは見た目上の行を基準にした diff は変です。たいてい変更された文は、見た目上の行の途中から始まるからです
  • cargo でインストールした場合、difftastic と関連ツールを更新する方法は次のとおりです
    cargo install cargo-update
    cargo install-update --list
    cargo install-update --all
    cargo でインストールできる面白い Rust プロジェクトもあります
    https://mise.jdx.dev/ mise-en-place は https://asdf-vm.com/ の高速で柔軟な代替品です
    https://github.com/ajeetdsouza/zoxide は優れた cd 代替ツールで、移動した場所を保存しておき、"z hel" のような部分一致だけで "~/projects/helloworld" に移動できます
    https://github.com/bootandy/dustdu を補完するツールで、どのディレクトリがディスク容量を多く使っているかを示します

    • さらに小気味よいツールを 3 つ挙げると、https://github.com/eza-community/eza は見た目がより良い lshttps://github.com/ClementTsang/bottom はグラフ付きの htophttps://github.com/sharkdp/bat は構文ハイライト付きの cat です
    • zoxide は本当に気に入っています。リストには lsd も追加する価値があります。より見栄えのよい ls です
    • mise-en-place をインストールしてみましたが、asdf がこうなってほしかった、まさにその姿です
    • 新しい du 系の中では dua-cli が一番気に入っています。ncdu のクローンで、対話型のターミナル UI です
      ncdu にライトモードがなかったので探していたところ見つけました
    • ncdu が断然最高の du 代替ツールです
  • git のようなツールが、なぜこういうものを標準で提供していないのか、ほとんど理解できない。
    difft を1年ほど使ってきたが、最大の不満は difft がない環境で他の diff ツールに戻りにくくなることだ。
    意味ベースの diff ツールも研究されているのか気になる。たとえば、構文は変わっているが意味は同じ場合を扱うツールのことだ。
    一般的な問題としては難解に見えるが、小さな DSL や一部言語のサブセットでは可能だったり有用だったりするかもしれない。

    • デフォルトでは古い diff を使うのが妥当だと思う。
      git を実行しようとするほぼすべてのシステムにすでにあり、速く、小さく、誰もが基本的な使い方を知っている。
      それでも、そのデフォルトを簡単に変更できるのは良いことだ。
    • https://semanticdiff.com/ を作っているところで、10進リテラルを16進数に変えたり、JSON オブジェクト内のキー順序を変えたりするような基本的な意味変化を検出する。
      コマンドラインユーティリティではなく、VS Code 拡張と GitHub App だ。
      仕組みや difftastic との違いが気になるなら、https://semanticdiff.com/blog/semanticdiff-vs-difftastic/ を見るとよい。
    • Difftastic は有用なツールだが、私の経験では git のように広く使われるツールのデフォルト選択肢にするには遅すぎる
    • こういうことをやろうとすると、diff ツールは事実上コンパイラになる。
    • 今 git に difft を組み合わせて試しているが、かなり気に入っている。
      それでもデフォルトにすべきではないと思う理由の1つは、空白の差分を隠す点だ。設定できるかどうかはまだ確認していない。
  • @@ -5,6 +5,7 @@ という構文を読めるかと尋ね、Difftastic は変更前後のファイルの実際の行番号を表示する、という説明には完全に同意する。
    すぐに導入して git diff を試してみたが、とてもよく動く。

    • その構文が読めないのか?
  • GitHub がこういうものを出していないのは、プロダクト革新の大きな空白のように見える。
    インデントだけが変わっても助けてくれる機能がなく、たいてい巨大な削除と追加として表示される。
    GitHub の diff ビューアはもっと賢くなれるし、そうあるべきだ。

    • GitHub は意味ベースの diff どころか、構文認識もまともにできない。
      実際、foo.cpp.infoo.mk.in が違うことすら区別できない。
      どんな foo.t でも Perl として宣言され、修正する方法もない。10年前からあるチケットも存在する。
    • tree-sitter は正確性ではなく、エディタで使うための性能に最適化されている。
      実際に TS の主要開発者たちも、文法の正確性にあまり気を使いすぎないよう勧めている[1]。
      GitHub や同じような立場のサービスには、この制約が致命的になり得るように見える。
      [1] https://github.com/tree-sitter/tree-sitter/issues/130#issuec...
    • GitHub には diff で空白を無視するオプションがある。
  • このツールに乗り換えようとしたが、変わっていない部分にも妙な書式を使うのでうるさすぎると感じた。
    結局 icdiff[0] に戻った。
    [0]: https://github.com/jeffkaufman/icdiff

  • 以前 Semantic Merge[1] を見つけたが、オープンソースではなかった。
    これはマージはせず diff だけだが、少なくともオープンソースで、diff がはるかに見やすいので、すでにデフォルトに変更した。
    マージ機能へ拡張する計画があるのか気になる。
    [1] https://docs.plasticscm.com/semanticmerge

    • GitHub README によると、difftastic はマージをしない。
      AST マージは難しい問題であり、difftastic は扱わないとのこと。
      また AST diff は、テキスト diff の観点では情報を失うプロセスでもある。difftastic は構文上意味のない空白を無視するが、マージには空白の追跡が必要だ。
    • 私も Semantic Merge を勧めようとしていた。
      大きなチームで C# プロジェクトの厄介なリファクタリングをしていた時期には、本当に救世主のようなツールだった。
  • 以前の議論:
    https://news.ycombinator.com/item?id=27768861 (297 points | 3 years ago | 61 comments)
    https://news.ycombinator.com/item?id=32746258 (698 points | 2 years ago | 90 comments)
    https://news.ycombinator.com/item?id=30841244 (983 points | 2 years ago | 219 comments)

  • 素晴らしいツールのようだが、大きな XML ファイルをいくつか確認してみると、変更された行は通常の緑、変更された属性は太字の緑で表示され、視覚的に区別しにくかった
    ドキュメントでは diff スタイルを変えたり、太字の場合に別の色を指定したりする方法を見つけられなかった。
    方法を知っている人がいるか気になる。

    • 残念ながら、まだ色のカスタマイズはサポートしていないようだ。
      この機能を支持すると、この issue[1] にコメントした。
      望んでいる機能が同じカテゴリなら、そこに意見を添えてもよいし、十分に別の機能だと思うなら新しい issue を開いてもよさそうだ。
      [1]: https://github.com/Wilfred/difftastic/issues/611
  • Arch Linux のパッケージは 7MB なのに、インストール後は 80MB に展開され、difft バイナリだけでも 78MB ある点が興味深い
    LZ4 圧縮を有効にした ZFS データセットでは、du は 17MB と表示されるとのこと
    バイナリ内でこれほどよく圧縮できる何かを、なぜそのまま圧縮しておかないのか気になる。RAM 上で展開すれば、もっと速くロードできる可能性もありそう

    • 圧縮の話が出たので気になって確認してみたところ、btrfs zstd:1 では約 10MB だった
      Processed 1 file, 614 regular extents (614 refs), 0 inline.
      Type Perc Disk Usage Uncompressed Referenced
      TOTAL 14% 10M 77M 77M
      none 100% 1.1M 1.1M 1.1M
      zstd 12% 9.8M 76M 76M
    • Nim パーサーがマージ不可能な 60MB の C ファイルだというコメントと関係がありそう
      Tree-sitter は非常に大量のコードを必要とするようで、そのかなりの部分は重複しているか、圧縮可能に見える
    • 実行ファイルは、丸ごと RAM にロードされてから起動するわけではない
      メモリマッピングされ、実際に使われる部分だけが必要になったときにロードされる
      そのため大きなバイナリを圧縮すると、デマンドページングが機能せず、かえって遅くなる
      数年前に 280MB の Windows EXE で試したところ、70MB 程度まで圧縮はされたが、元のものより起動に数秒余計にかかった
      ネットワーク越しにバイナリを実行する場合のような一部のシナリオでは理にかなうかもしれないが、ほとんどの場合は圧縮していないバイナリのほうが起動が速い