最近gitのコンフリクト解消職人みたいになっていてすごくつらいです。 普通のプログラムであれば順番が重要なので手動でのコンフリクト解消は避けられないのですが、 僕が相手にしているのは最終的にMySQLに食わせるデータなのでそこまで順番は重要ではありません。 順番に挿入したところで、MySQLが順番にかえしてくれるとは限りませんからね。 このようなケースではある程度機械的にマージできるのでは?と調べてみました。
merge driver
いろいろググってみるとgitattributesでファイル毎にマージの細かい挙動を制御できるようです。 通常マージの方法はgitがよしなに選択してくれますが、merge属性に以下の項目を指定することでマージの方法を強制することができます。
- text
- テキストファイルとしてマージする。
- コンフリクトすると
<<<<<<<,=======,>>>>>>>でコンフリクトした場所を教えてくれる。
- binary
- バイナリファイルとしてマージする。
- コンフリクトするとマージしようとしたファイルを残しておいてくれる。
- union
- テキストファイルとしてマージする。
- textと違ってコンフリクトしてもマーカを付けない。どちらの変更も残すように適当にマージしてくれる。
- 適当なので コンフリクト時の行の順番は保証されない
text, binaryはコンフリクトしたときによく見る挙動ですね。 unionは初めて知ったので、簡単なレポジトリを作って挙動を確かめてみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
わざとコンフリクトを起こしてみるテストです。 ファイル末尾にEveとBobをそれぞれ別々のブランチで追加したためコンフリクトしてしまっています。
では次にgitattributeを追加してmerge=unionを指定した場合に挙動を確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
通常はコンフリクトするケースですが、今度はうまくマージできました。
merge driverをカスタマイズする
デフォルトではtext, binary, unionしか用意されていないmerge driverですが、.git/configをいじることで自前のmerge driverを追加することができます。
unionでは行の順番が不定になって不便なので、試しに「必ずソートされており重複がないファイルをマージする」ためのmerge driverを作ってみます。
まずはマージするためのコマンド用意しましょう。
1 2 3 4 5 6 7 8 | |
パスの通った場所にこのファイルを置き、.git/configにこれを呼び出す設定を書けば、gitattributeから使用できるようになります。
1 2 3 | |
%A: 現在のブランチの状態%B: マージしようとしているブランチの状態%O: 共通の祖先の状態%L: コンフリクトマーカの長さ。1.7から使えるらしい%P: ファイルのパス。2.5.0から使えるらしい
このmerge driverを使ってマージすると、先の例ではAlice, Bob, Eveの順番で並ぶようになります。
theirs-oursの順番に並べてみる
僕のケースではtheirs-oursの順番で並んでくれると都合が良いので、こんなスクリプトを書いてみました。
1 2 3 4 5 6 7 8 | |
1 2 3 4 | |
あとは勝手にコンフリクト解消して欲しいファイルに対して
.gitattributesでmerge=theirsoursを指定すれば通常はコンフリクトする場合でもマージしてくれます。
ただ、さすがに全自動だとちょっと怖いので、以下の様にコンフリクトするようであればユーザに確認(exit 1するとコンフリクトした扱いになる)
したほうが無難な気もしますね。
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
まとめ
gitattribute便利。 gitattributeを使ってGit Diffでcsvの差分を見やすく表示するのもどうぞ。
ただmerge driverからはファイルのメタ情報に触れないので、「コミット日時が新しい方を残す」みたいなことができないのが残念です。
ブランチ決め打ちにするのはちょっと怖いし、
merge strategyのカスタマイズは大変そう・・・
(一応 git-merge-hogehoge をいうコマンドを用意しておけば git merge --strategy hogehoge と使えるようです。が、git-merge-hogehoge <base>... -- <head> <remote> ... の形式で渡ってくるので、そこから再実装するのはつらい・・・)