はじめに
こんにちは、kenです。みなさんコンフリクト解消してますか!
チーム開発をしているとコンフリクトとは嫌でも向き合うことになりますが、コンフリクト解消って緊張感のある作業なのでやりたくないですよね。
そんなコンフリクト解消をちょっぴり楽にする(かもしれない)コマンドを最近知ったので今回はそれを紹介します、その名もgit rerere
です。
git rerereとは
Gitの公式ドキュメント(日本語版)には次のように記載されています。
git rerere コマンドはベールに包まれた機能といってもいいでしょう。これは “reuse recorded resolution” の略です。その名が示すとおり、このコマンドは、コンフリクトがどのように解消されたかを記録してくれます。
そして、同じコンフリクトに次に出くわしたときに、自動で解消してくれるのです。
ここに書かれているように、git rerere
は以前に解決したコンフリクトを再利用することで、同じコンフリクトに再度遭遇した際、自動で解決してくれる機能です。
一般的にコンフリクトは、どの変更を採用するかを決めるために意図を理解している人が手動で解決する必要がありますが、同じコンフリクトが再発する場合は、前回と同じ解決策を適用できることがほとんどです。そんなときにgit rerere
は非常に便利です。
しかし、このコマンドを使ったことがない人の中には「本当にコンフリクトを自動で解消してくれるの…?」と疑われる方もいらっしゃると思います(現に私がそうでした)。
そのため、次の章では実際に使っている様子をお見せしようと思います。
実際に使ってみる
本当に同じコンフリクト解消を自動で行ってくれるのか実際に試してみます。
まずはコンフリクトをわざと起こしますが、具体的なコンフリクト内容は知らなくてもいいので興味のある人だけ読んでください。
起こしたコンフリクトの詳細
このデモのために作ったリポジトリ上のmain
とfeature
ブランチ間でコンフリクトを起こします。
最初main
ブランチのhello.txt
は次のような状態でした。
こんにちは
こんばんは
そこからfeature
ブランチを切り、この2つの挨拶に伸ばし棒を付け加えました。
こんにちはー
こんばんはー
それに並行してmain
ブランチで2つの挨拶にビックリマークを付け加える変更をしました。
こんにちは!
こんばんは!
この状態でmain
ブランチをfeature
ブランチにマージしようとするとコンフリクトが発生しますね。これで準備が完了です。
コンフリクトが起こせる状態になったのでgit rerere
の出番です。
ここまで書いていませんでしたが、git rerere
はgit config
で編集できるgitの設定のうちのひとつです。そのため、有効化したい場合は次のコマンドを打ちます。1
git config --global rerere.enabled true
まずは比較のためrerere
が無効になっている状態(デフォルト)を見てみます。
feature
ブランチ上でgit merge origin/main
を実行しorigin/main
とのコンフリクトを解消。その後git reset --hard HEAD^
でマージコミットを打ち消し、再度origin/main
とのマージを実行すると、二回目のマージ実行時も同じコンフリクトを解消しないといけなくなりました。これが普通ですよね。
次はrerere
を有効にした状態で同じ操作をしてみます。
すると1回目のマージ実行時には先ほどと同様にコンフリクトの解消が求められましたが、2回目のマージ実行時にはすでにコンフリクトは解消されており、手動でのコンフリクト解消は必要なくgit add
してcommit
するだけになりました!
よくみると2回目のマージ実行時にはResolved 'hello.txt' using previous resolution.
と出ており、以前のコンフリクト解消の結果を使っていることがわかります。
以前のコンフリクト解消の記録をどこに保存しているのか
このgit rerere
、一体どんな仕組みで動いているのか気になると思うので、その中身についても少し触れようと思います。
git rerere
を有効にしてコンフリクト解消を行うと、作業を行ったリポジトリの.git
配下にrr-cache
というディレクトリが生成されていると思います。ここにコンフリクト解消の履歴は記録されていきます。
たとえば先程のデモで行ったコンフリクト解消の後にこのrr-cache
ディレクトリを覗くと、ハッシュ値の名前でフォルダが作られており、その中にはpostimage
とpreimage
という名前のファイルが入っています。
preimage
は コンフリクト発生時のファイルの状態 を記録します。先程の例で生成されたpreimage
は以下の通りです。
<<<<<<<
こんにちはー
こんばんはー
=======
こんにちは!
こんばんは!
>>>>>>>
一方postimage
は、コンフリクト解消後のファイルの状態を記録します。次に同じコンフリクトが発生したとき、このファイルの内容が再適用されます。先程の例で生成されたpostimage
は以下の通りです。
こんにちはー!
こんばんはー!
まとめると、コンフリクト解消の履歴は.git/rr-cache
ディレクトリに保存され、preimage
とpostimage
がそれぞれコンフリクトの解消前・後を記録しているというわけです。2
やっていることはかなり原始的でしたね。
具体的な使用場面
ここまでで、同じコンフリクトの解消においてgit rerere
が非常に便利であることをお伝えできたかと思います。最後に、このコマンドが有効に使える場面、すなわち具体的に同じコンフリクト解消が発生する場面についても少し触れておきたいと思います。これらの例は、冒頭でリンクを貼ったGitのドキュメントに書かれているものです。
-
長期にわたって開発が続いているトピックブランチをいつでもmainブランチに問題なくマージしたいが、そのためのマージコミットが複数生まれるのを避けたいとき。
(こまめにmainをマージしてはマージコミットを取り消すことで実現できますが、その際にrerereを有効にしておくと、同じコンフリクト解消を繰り返さなくて済みます。)
-
リベースする度に同じコンフリクトを処理することなく、ブランチをリベースされた状態に保っておきたいとき。
(頻繁にリベースを行う場合でも、rerereを使うことで毎回同じコンフリクトを解消する手間が省けます。)
-
多くのコンフリクト解消を乗り越えてようやくマージをしたあとに、リベースを使うことに方針を変更することになったとき。
(最初にマージを行った際にrerereが有効になっていれば、リベースに方針を変更しても同じコンフリクトを再度解消する必要がなくなります。)
-
開発中のトピックブランチをいくつもまとめてマージして、テスト可能なHEADを生成するとき。
(テストが失敗した場合、マージを取り消して失敗の原因となったブランチを除外して再度テストを実行しますが、rerereを使うことでその際のコンフリクト解消が不要になります。)
これらの場面では、git rerere
が効果的に機能し、開発効率を大幅に向上させることができると思います。
さいごに
今回はgit rerere
について書いてみました。
コンフリクト解消の履歴を記録して自動的に解消する以上、間違ったコンフリクト解消が記録されるとそれも勝手に適用されてしまうため注意が必要ですが、便利な機能であることは間違いないので使い所を見極めてうまくつかっていきたいですね。
間違いなどありましたらコメントにてご指摘ください、ここまで読んでいただきありがとうございました。