📜

Unified Diffに関する覚え書き

2022/12/10に公開約2,800字

ddu-source-git_diffを実装しててUnified形式のdiffをパースする必要があったのでメモ

  • 自分が必要な範囲でしか書いてないので参考資料の方が詳しい
  • Unified形式はdiffutilsなどが出力するフォーマットの1つでこのような見た目をしている(データはdiffutilsのマニュアルより抜粋)
    --- lao	2002-02-21 23:30:39.942229878 -0800
    +++ tzu	2002-02-21 23:30:50.442260588 -0800
    @@ -1,7 +1,6 @@
    -The Way that can be told of is not the eternal Way;
    -The name that can be named is not the eternal name.
     The Nameless is the origin of Heaven and Earth;
    -The Named is the mother of all things.
    +The named is the mother of all things.
    +
     Therefore let there always be non-being,
       so we may see their subtlety,
     And let there always be being,
    @@ -9,3 +8,6 @@
     The two are the same,
     But after they are produced,
       they have different names.
    +They both may be called deep and profound.
    +Deeper and more profound,
    +The door of all subtleties!
    
    • diffutilsのマニュアル曰くWayne Davison氏が設計した物らしい
    • 人にも見やすいからか広く使われており、git diffなどのツールは標準でこれを吐く
      • 今回パースすることになったのはgit diffの出力を扱いたかったため
  • このフォーマットはヘッダーとブロックのような構造から成り立つパッチを1つの単位とし、1つのファイルに複数のパッチを持てる
    • パッチは以下の定義から成る
      • ---及び+++で始まるファイル名の定義
        • ヘッダーみたいなもの
        • ---が旧ファイル、+++が新ファイル
        • 次にファイル名。Tab以外の任意長の文字列(何を受け付けるのかは調べてない) + 任意でTabとコメントから成り立つ
          • 上の例だと分かりづらいけどlaoと日付の間にTabがある。日付はコメント
            • diffutilsは日付を入れるがgit diffは入れない。gitにとってファイルのタイムスタンプは重要ではないということだろう多分
      • @@で始まるヘッダーと正規表現で言う所の[ +-]で始まるdiff本体から成るハンクが任意個
        • ヘッダーの意味はこう。@@ -(古いファイルの位置),(古いファイルのハンクの長さ) +(新しいファイルの位置),(新しいファイルの長さ) @@
          • 長さについては1の場合は省略できる
          • git diffも省略するので対応しないといけない
      • 本体は先頭1文字が指示、その後は実際の行の中身
        • -だと新しいファイルから削除される
        • +だと新しいファイルに追記される
        • 何も付いていない場合はそのまま使われる
    • git diffの場合先頭に以下のような出力がくっついているがUnified形式のお気持ちからするとこれはコメント
      diff --git a/file b/file
      index aaaaaaa..bbbbbbb 100644
      
      • gitにとっては意味のある出力らしいので注意
  • 意味のある単位で切り出すには以下のことをやるといい
    • フォーマットが正しいと仮定して行っているので人が編集しているのを想定するとエラーハンドリングなどの面で不適格
      • patchgitなどの正しく処理しているプログラムを参照すべし
    • ファイルを先頭から走査し---で始まる行を探し出す
    • 2行分切り出して飛ばす
    • @で始まってるか見る。もしそうでなければ今まで切り出したデータを1つのパッチとして別の場所に切り出す。これを繰り返す
      • 先頭行をパースしハンクの長さをそれぞれ取り出す
      • 新旧それぞれ用にカウンタを用意する
      • 各行に以下の処理を適用
        • -だったら旧カウンタを+
        • +だったら新カウンタを+
        • だったら両方を+
        • カウンタがハンクの長さと全て一致したらハンクの終わりと見なし、それ以前の行を全て切り出す
    • パッチの切り出しが終わったら最初に戻って同じことをファイルの終端まで繰り返す
    • 参照実装はここにある
GitHubで編集を提案

Discussion

ログインするとコメントできます