やりたいこと
railsのgenerator、例えばrails g controller Root index
などを実行した時、生成対象のファイルが既に存在し、生成したものと内容が違った場合、
Running via Spring preloader in process 26159
conflict app/controllers/root_controller.rb
Overwrite /path/to/app/controllers/root_controller.rb? (enter "h" for help) [Ynaqdhm]
の様な選択肢が出てきてm
キーを叩くとマージできるのですが、その時起動するmergetoolをvscodeに変更します。
やりかた
シェルスクリプトを作ります
#!/bin/bash
tmp_path=$1
destination_path=$2
orig_path=${destination_path}.orig
# オリジナルを一度別名でバックアップ
mv ${destination_path} ${orig_path}
# 二つのファイルを見比べる基準、共通の祖先となるファイルを空で生成
touch ${destination_path}
# mergeする
git merge-file\
--stdout\
-L ORIG\
-L BASE\
-L GENERATED\
${orig_path} \
${destination_path} \
${tmp_path} > ${destination_path}
# vscodeを起動して、オリジナルのバックアップを削除。
code --wait ${destination_path} && rm -f ${orig_path}
これをどこかパスの通ってる場所に適当な名前でおきます。今回はmergetool_for_rails_generator
という名前でおきました。
環境変数を設定する
# ~/.bash_profile
...
export THOR_MERGE=mergetool_for_rails_generator
反映する
source ~/.bash_profile
これで変更かのです。
なぜこんなめんどくさいか?
git mergetoolをvscodeに変更する方法は散見し、これは期待通り変更できるのですが、これを変えてもgeneratorで起動するmergetoolは変わりません。
変わらないというかsh: vscode: command not found
というエラーで動かなくなります。
なぜそんなことが??と思って保存してるソースを見ると
def merge(destination, content) #:nodoc:
require "tempfile"
Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
temp.write content
temp.rewind
system %(#{merge_tool} "#{temp.path}" "#{destination}")
end
end
def merge_tool #:nodoc:
@merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool
end
def git_merge_tool #:nodoc:
`git config merge.tool`.rstrip rescue ""
end
こんな感じでgit config merge.tool
から採れたコマンドを"#{temp.path}" "#{destination}"
という引数で呼び出しているからですね。
ここがハマった
git merge-fileというコマンドがあって、コンフリクトが起きた時よく見る
<<<<<<< A
lines in file A
=======
lines in file B
>>>>>>> B
のようなファイルを生成してくれるので使いたかったのですが、これの引数の渡し方がよくわからなかったです。
git merge-file fileA fileB fileC
オプションはさておき、三つの引数が必要なのですが、「fileBとfileAの差分」と「fileBとfileCの差分」をfileBにマージしてfileAに書き込むという難しい仕様です。
要はfileBが共通の祖先で、そこから先の変更のみに注目してマージすると言うことですね。書き込み先ファイルも変更したかったので--stdout
で書き出しを抑制し指定したファイルに書き込んでます。
generatorの場合、例えばcontrollerだったら、本当はクラス定義部分などは共通の祖先として指定したいところではあります。それができればオートマージが成功する可能性も上がると思われます。
独自generatorであればテンプレートにコメントでマークをつけておいて「共通の祖先」を生成することも不可能ではないと思いますが・・・ファイルの種類も多いしとりあえずこれでお茶を濁します。