⭐️Mergiraf

https://mergiraf.org/

Git のマージコンフリクトを構文木(AST)ベースで解決する merge driver。Git 標準は「行」しか見ないが、Mergiraf は言語の文法を理解して両者の変更を突き合わせる。

base に import json だけある状態から、2人が別々に import を足す。

# base        # ブランチA      # ブランチB
import json   import json      import json
              import os        import sys

Git には「同じ場所に別の行」= コンフリクト。Mergiraf は import が「順序が意味を持たない列挙」だと文法から分かるので、両方採って import json / os / sys を返す。

コンフリクトとして認知されなくなる?

ほぼその通り。ただし git が見落とすのではなく:

  1. git が行レベルで衝突を検出する
  2. .gitattributes 経由で merge driver(= Mergiraf)が呼ばれる
  3. 解けたら成功として git に返す → 人間の目にはコンフリクトが出ない
  4. 解けなければ従来どおりマーカーを残す

握りつぶしではなく「driver が代わりに解いた」。3 は mergiraf review で後から確認できる。

base / 自分側 / 相手側(3-way マージ)

main で git merge feature したときに git が見ている3つ。

  • base(%O)= 枝分かれした地点の共通祖先
  • 自分側(%A / ours)= いまいる main
  • 相手側(%B / theirs)= 取り込む feature

base と見比べて片側だけ変わっていればそちらを採る。両方が同じ場所を変えていたら base では決められない → コンフリクト。base 無しでは「追加されたのか消されたのか」も判別できないので3つ要る。

コンフリクト中は3つとも index に残っている。: の後ろの数字は index の stage 番号(単なる例ではない)。

git show :1:path/to/file   # base
git show :2:path/to/file   # 自分側 (ours)
git show :3:path/to/file   # 相手側 (theirs)
git ls-files -u            # stage 番号つきで一覧

普段 index は1パス1エントリ(stage 0 = マージ済み)。コンフリクト中だけ同じパスが3エントリに分裂する —— これが「コンフリクト状態」の正体。3つが一致すれば git が自動で stage 0 に畳む。

mergiraf review は何をしている?

前提として、Mergiraf は1回のマージで2通りの答えを作っている。

  1. 行ベースの結果 —— git 標準と同じやり方で出した答え
  2. 構文木ベースの結果 —— Mergiraf 独自の答え

両方をキャッシュに残し、採用したほうを git に返す。

review はその2つを並べるだけ。

git diff --no-index <行ベースの結果> <採用した結果>

出てくる差分 = 「git 標準ならこうなっていた」との違い = Mergiraf が独自に手を入れた箇所そのもの。行ベースをそのまま採用した場合は差分ゼロになる。

対応言語

33言語 + 11のデータ形式(Go, Python, Rust, TypeScript, Java, Ruby … / JSON, YAML, TOML, XML, HTML, go.mod …)。mergiraf languages で一覧できる。

拡張子で言語を判定し、未対応・パース失敗なら黙って行ベースにフォールバックする(= git と同じ挙動に戻るだけ)。だから * merge=mergiraf と全ファイルに当てても壊れない。

そもそも merge driver とは? デフォルトは?

git がマージ時にファイルごとに呼ぶ「解決係」。.gitattributes でパスごとに割り当てる。組み込みは3つ:

driver挙動
textデフォルト。行単位の3-wayマージ。衝突箇所に <<<<<<< を残す
binaryマージを試みず自分側を残し、コンフリクト扱いにする(バイナリ用)
union衝突した行を両方採用して並べる。順序は保証されない