- 従来の Unified Diff フォーマットには、開発環境の要件を十分に反映できていないという限界がある
- DiffXは既存形式と完全に互換性があり、将来を見据えた構造とメタデータ拡張性を提供する
- 複数のコミット情報やバイナリファイル、文字エンコーディングおよびメタデータを構造化された方法で保存できる
- 標準化されたパース規則の導入により、さまざまなツール(パッチ、コードレビューなど)と容易に連携できる
- 既存のツールやワークフローでも問題なく利用でき、新機能のみ該当ツール側の対応が必要
開発者と Diff ファイル
- ソフトウェア開発者は通常、Git、Subversion、CVS などで diff ファイルを使ってコード変更内容を確認する
- Diff ファイルは、テキストの追加(
+)・削除(-) とファイル名、パス、タイムスタンプ、一部のメタデータなどを含む構造である
- ほとんどのツールとユーザーは Unified Diff 形式を利用しており、この方式は比較的シンプルに差分を可視化する
Unified Diff の限界
- Unified Diff は、ファイル識別、変更範囲、追加・削除された行だけを標準化しており、エンコーディング、リビジョン、拡張メタデータ などは標準化していない
- 多様なソース管理システムのサポート、信頼性の高いパース、豊富な情報抽出が難しい
- 次のような問題が継続的に発生している
- 複数のコミットを一度に表現できない
- バイナリファイル向けの専用標準形式が不足している
- 文字エンコーディングが分からず、情報損失や混乱が発生する
- 任意メタデータの標準化が不十分なため、ツールごとに形式が異なる
改善の方向性
- 既存の Unified Diff は構造と標準が不足している一方で、柔軟で多様な環境にすでに広く普及している
- Git Diff が事実上の標準の役割を果たしているが、依然としてフォーマットの公式仕様と汎用的な拡張性は不足している
- 既存の Unified Diff の長所を活かしつつ、拡張性と標準構造を加えた新しい形式の必要性が高まっている
DiffX とは何か
- DiffX は拡張可能な Diff フォーマットであり、既存ツールと完全に互換で、人間による可読性を維持しながらメタデータと構成を構造的に格納できる
- 構文例:
- ファイル、コミット、diff 全体などに対して、メタデータと本文を構造的かつ拡張可能な方式で保存する
- サンプル出力には
#diffx: のような構文と、section、メタデータ(JSON)、ファイルパス、コミット情報などが含まれる
DiffX の主な特長
- 標準化されたパース規則を提供し、ツールが信頼性高く情報を読み書きできる
- メタデータ保存・管理の公式化: diff 全体、コミット、ファイル単位ごとに利用できる
- 既存のパーサー、パッチャー、コードレビューなどあらゆるツールと互換である(新機能は対応が必要だが、既存機能の互換性は保証される)
- 1つのファイルで複数のコミット、バイナリ diff、テキストエンコーディング情報など複数の内容を効率的な構造で表現できる
- ツールが diff を開いて必要な情報を記録・修正し、再保存できる変更可能性(mutability)をサポートする
DiffX の目指すものと目指さないもの
- すべてのツールにフォーマット対応を強制したり、互換性問題を生じさせたりしない
- ベンダーロックインを招かず、既存ワークフローを壊さない
- 既存の Diff ファイルの問題点を解消し、開発・レビュー・分析ツールで一貫性と信頼性のある利用体験を提供する
1件のコメント
Hacker Newsの意見
私は
..metaや...metaのような階層的に複雑なフォーマットが好きではない。表現の明確さのためには、diff 全体、ファイル、チャンクの3段階だけに分け、それぞれ別の名前を与えるほうが、より分かりやすいフォーマットになると思う。メタブロックがなくても一目で対象を区別でき、ミスやエラーも減らせる。diff 全体のメタデータとファイル単位のメタデータが同じフィールド構成なのも不合理だ。そして、なぜ JSON と key=value の2つのフォーマットが必要なのか分からない。管理対象が少ないなら、1つのフォーマットだけを使うほうが実装や既存ツールとの統合にはるかに有利だ(grep、sed、jq のどれか1つで十分)。加えて、リストで trailing comma を許可してほしいし、diff は本来分割適用できる構造なのに、このフォーマットがその点にどう影響するのかも気になる(たとえば diff の一部だけを適用するにはプリアンブルをコピーし、ブロックも別途コピーしなければならず、面倒だと思う)。revision はファイル属性なのか、それともコミットチェックサムなのかも気になる。#<section_level><section_type>という形に整理した。各メタブロックでは縦方向にレベルだけを確認すればよく、ドットの数を数えれば自然にどのレベルのメタかを判別できる。キー/値ヘッダーフォーマットは、パーサーが事前に知っている単純な属性だけを入れるためのもので、自由なメタデータは別の meta ブロックに入れる設計にした。既存の JSON だけでなく、将来ほかのシリアライズ方式が必要になったときにも拡張できるよう、ヘッダーにフォーマットを明記できる。これは単純さと柔軟性の間でバランスを取ろうとした結果だ。trailing comma は個人的には入れたいが、ベースレベルの JSON 互換性の問題があるため、JSON5 パーサーを必須にはしづらい。diff は依然として分割可能であり、Unified Diff が無視する領域に情報を入れているおかげで GNU patch などでは無視され、問題はない。ただし、2つの DiffX ファイルに分割するならヘッダーを新たに追加する必要があるため、少し複雑になることはある。SCM によっては diff を分割すると一部のメタデータ(たとえば parent commit 情報)が失われたり、適用対象によって情報損失が起こることもある。revision は SCM ごとに異なり、コミット ID、ファイルごとの ID、その組み合わせ、追加情報など非常に多様な要件がある。SCM ごとのさまざまな要求を満たせるよう考慮した構造だ。私には、下の4つの指摘のうち、diff ファイルの一般化として実際に妥当なのはバイナリパッチ表記法の1つだけに見える。残りは特定のバージョン管理システム(SCM)内部のデータやプロトコルの問題で、それぞれのクライアント、サーバー、バックアップシステムの中でしか通用しない話だ。ほかはすべて不要に思える。
1つの diff で複数コミットを列挙できない
バイナリパッチの標準がない
テキストエンコーディングを認識できない(地味に問題)
任意のメタデータの標準フォーマットがない
私たちは20年間で12種類以上の SCM を連携するコードレビュー製品を開発してきて、想像もつかないような diff フォーマットや SCM ごとの問題を数えきれないほど経験してきた。実際にエンドユーザーが直接気にする問題ではないが、ツール開発の側では必ず解決しなければならない pain point だった。SCM の中には独自の diff フォーマットを持たないものや、情報が欠けていて(たとえば削除ファイルを表せない)、他のツールがファイルを正しく識別できなくなるものも多く、だからこうした改善が必要だと感じた。
最近では少し珍しくなったが、私も patch(1) に似たツールを今でもときどき使う。複数プラットフォームの開発者が協業すると、ファイル名の大文字小文字や文字エンコーディングの問題などで、さまざまなトラブルが今でも起きる。
JSON を長さ情報付きの self-delimitered フォーマットとして埋め込む方式だと、JSON 内容の空白を1文字変えただけでも JSON 自体は有効なのに DiffX 全体が壊れる危険がある。独自ヘッダーと JSON ペイロードが混在している点、ドットの数を数えないと別の meta ブロックを見分けられない点、2種類のパーサーが必要な構造である点も含め、構造的に clunky で messy な組み合わせに感じる。メタデータ用の拡張 diff を標準化しようという発想は良いが、今回の実装は試行錯誤の途中のように見える。
patch フォーマットがこうした問題をすべて解決していると思う。git format-patch の説明リンク
今日初めてこういうフォーマットがあると知って、参考になった(私はただの普通のインターネット利用者で、作者ではない)。
git なら解決できるが、Review Board のような製品だと SVN、CVS、Perforce など複数の VCS と統合しなければならないため、こういうフォーマットが出てきた背景なのだろう。私も Review Board と SVN を使ったことがあるが、複数の開発者が git-svn と svn を混在させていて、レビュー時の diff アップロードでしばしば問題があった。両方がサポートする標準 diff フォーマットがあれば、ツールの利用にはるかに役立ったはずだ。
私は、提示されている問題が実際に存在するとあまり感じない(バイナリファイルは除く)。
エンコーディングが違っても patch アルゴリズムは同じなので気にする必要はない(文字が有効な utf-8 である必要すらない)。
1つの diff に複数コミットを入れたい場面もない。複数の diff に分けるほうが直感的だ。
メタデータはシステム内部でのみ有効なのではないかと思う。
エンコーディングについても、patch data はどうせ ascii ベースのバイナリデータのように扱うべきで、mixed encoding に対してファイル修正もあり得るので、エンコーディング固定の意味はあまりない。
まったく問題ではないと思う。diff を実際によく使うユーザーの現実の経験を聞くほうがよく、既存でうまく動いているフォーマットをわざわざ overengineering しないほうがいい。
バイナリデータの問題は確かに存在すると思う。
たいてい、実際にツールを作ったり特定の SCM とインターフェースしたりするときにだけ、こうした問題に直面する。
文書全体が読みにくく感じる。私にとって「diff」とは2つの項目(ファイル、ディレクトリなど)の差分を指すが、TFA で言っている diff は、私の知る「patch」だ。ここで議論されているのは diff ではなく patch メタデータ管理の話だ。メタデータを JSON のような必須フォーマットとして標準化するなら悪くないが、中途半端に self-describing な length-delimited 構造になっていて、問題を隠しているように感じる。標準化自体は良いが、もっと明快に整理されたソリューションが必要だと思う。私には git diff スタイルが事実上の標準により近いように見えるのも面白い。
このフォーマットが何の問題を解決しようとしているのか気になる。パッチ/ディフフォーマットが十分によくないと言うなら、誰のための改善なのか、GNU Patch コミュニティで不満が増えているのか、何が理由なのかをもっと具体的に示すべきだ。実際により良いパッチフォーマットが本当に必要な理由がまだよく分からない。
長すぎてここには書けない文章を別にまとめているのだが、要約すると SCM を直接作る人や SCM と連携するツールの開発者向けの話だ。一般ユーザーなら気にする必要はない。diff フォーマットも SCM ごとに全部違い、本当によくできたものもあれば、深刻に不十分なものやフォーマット自体が存在しないものもある。複数の SCM をカバーしなければならない Review Board のような製品にとっては、こうした統合標準が実務上どうしても必要だ。実際に SCM ベンダーと協力して得たフィードバックを反映した改善の試みでもある。
Review Board 中心で使われるフォーマットのように見えるが、この製品は多様な VCS をサポートし、ソースレビューで diff が中核なので導入したのだろう。
最も一般的で明快な diff 表現は、2つのファイルをそのまま含める方式だ。今ではデータ容量は問題にならないので、
diff a b | patch cの代わりにapply a b cのようにすれば、内部表現が何であっても構わない。diff は人間には読みにくく、カラーのサイドバイサイドビューのほうがずっとよいので、そもそも両方のファイルを受け取って処理するほうが直感的だ。あえて標準化されていない diff を転送する必要はないと思う。2つのファイルから作る diff は1種類だけではなく、目的に応じたさまざまなバージョンがあり得る。diff フォーマットがあれば、diff 生成と適用のロジックを分離して n*m 問題を n+m 問題に減らせる。
プログラム更新などで毎回 130GB 全体を新しく受け取るのはうんざりだが、ほぼ同一のファイル同士なら圧縮もしやすく、2つのバージョン間の差分だけを送る方式にも十分な実利があると思う。元ファイルのハッシュ値だけを送って圧縮ファイル本体を転送するなど、さらに効率的な方法も考えられる。
2つのファイルペアの転送や管理は、専用コンテナなしでは難しいし、複数の変更(10ファイルの修正+削除+追加など)をメールなどでやり取りするなら、むしろ tar/zip のような複雑な構造が必要になり、pre-VCS 時代に逆戻りする感じがする。
diff の代わりに完全なファイルを送るほうが直感的だとしても、実際の用途や環境によっては diff は依然として重要な意味を持つ。最近のように LLM でコード編集などの結果を生成する際、diff で依頼するとトークンを大きく節約でき、応答遅延も 5〜10 倍まで減るなど、顕著な効率向上があった。両方のファイルを送るのはトークンの無駄とコスト増につながる。コードサンドボックスやリモートマシンに素早く適用するには diff に大きな最適化上の利点がある。ファイル A と A2、diff AxA2 があれば A2 の再構成も容易で、ストレージ最適化にも使える。マージ時に競合があるなら、そのときだけ人が介入すればよい。要するに diff は素晴らしい。
diff ツールが改行単位に依存しすぎている点には、今でも不満がある。1行が長すぎるとき(たとえば JSON や長い配列など)、レビューがしづらい。
私も同感だ。構造化データ(たとえば AST diff など)で、より良い diff 表現を探る余地は大いにあると思う。このフォーマット(DiffX)は既存の Unified Diff フォーマットの拡張であることを重視しており、もし AST などもっと具体的なフォーマットが広く使われるようになれば、それも容易に埋め込み・サポートできるよう設計されている。
よく使われる形式は、人間の可読性とツールのパース性の妥協点なので、中途半端な構造になっている。今回はメタデータ拡張で問題の一部を解決しようとしたようだが、本当に良い解決策は、平文ではなく、読みやすくてパース可能な新しいフォーマットを定めることだろう。長い行や構造化データに対して、従来より良い diff アルゴリズムを作るのは難題だが、十分に解決可能だと思う。
git は line diff より細かい word diff もサポートしており、デフォルトの区切りは空白だ。
JSON が唯一サポートされているメタデータフォーマットである点に疑問がある。汎用目的で設計されたメタデータ標準としては、JSON はむしろ複雑だ。