git-rerereのメモ
git-rerereってなんかレレレのおじさんみたいですが(Reuse recorded resolution of conflicted merges だそうな)、同じような衝突を何度も起こす状況で使うととっても便利なようで、調べつつ、メモ。
Linusが言っている「無駄なマージコミットやめて」を実現するには、rebaseがあればいいよね、と思ってたんだけど、既に公開しているようなブランチとなると、rebaseするわけにもいきません。
でも途中でちょっとだけ本線とマージしてテストしてみたくなったり、マージした後でやり直して再度マージしてみたくなったりも、しがちです。
そうなるとキツいのが、分かりきってるようなコンフリクトの解消。同じようなマージを繰り返すと、同じように衝突してるところを何度も手で直す作業を繰り返しやるハメになって、泣きそうになります。かといってマージを限界まで我慢して一発でFAというのも、キツい。そこでrerere。
衝突する状況
こんなファイルがあるとして。
<html><body> <h1>test</h1> <div> <p> こんぬつわ </p> </div> </body></html>
gitでmasterブランチと、そこから枝分かれしたtopicブランチを作り、それぞれで変更を行いました。
masterブランチ(4行目を変更)
<html><body> <h1>test</h1> <div id="hello"> <p> こんぬつわ </p> </div> </body></html>
topicブランチ(4行目と6行目を変更)
<html><body> <h1>test</h1> <div class="welcome"> <p> こんにちは </p> </div> </body></html>
こんな状況です。
$ git show-branch ! [master] divタグにidを追加 * [topic] "こんにちは"に変更。class追加 -- * [topic] "こんにちは"に変更。class追加 + [master] divタグにidを追加 +* [topic^] initial
よくある図だとこんな感じ。
o---o topic / o---o---o master
この状態でマージをしたら、4行目のdivタグの行がぶつかりそうです。
rerereを有効にしてマージ
topicブランチはその成果をいつかmasterブランチに戻したいんだけど、枝分かれした後にmasterブランチの方も進化しているので、その分を取り込まないとテストができません。
取り込む際には、同じ行を変更しているので、きっとコンフリクトします。
とりあえずrerereを有効にします。
全体的に有効にする場合 ~/.gitconfig に以下のように設定する
[rerere] enabled = true
とりあえずこのリポジトリで有効にする場合
$ mkdir .git/rr-cache
topicブランチにmasterブランチが進化した分を取り込んでテストしたい。
マージ
$ git checkout topic Switched to branch 'topic' $ git merge master Auto-merging index.html CONFLICT (content): Merge conflict in index.html Recorded preimage for 'index.html' Automatic merge failed; fix conflicts and then commit the result.
コンフリクトした。自動マージ失敗。
でも Recorded preimage for 'index.html' とか出てます。rerere発動。
$ git status index.html: needs merge # On branch topic # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # unmerged: index.html # no changes added to commit (use "git add" and/or "git commit -a")
予定通りの展開。
$ git diff diff --cc index.html index b21ded8,3a98e93..0000000 --- a/index.html +++ b/index.html @@@ -1,9 -1,9 +1,13 @@@ <html><body> <h1>test</h1> ++<<<<<<< HEAD + <div class="welcome"> ++======= + <div id="hello"> ++>>>>>>> master <p> - こんぬつわ + こんにちは </p> </div>
4行目をいい感じに修正します。
<html><body> <h1>test</h1> <div id="hello" class="welcome"> <p> こんにちは </p> </div> </body></html>
master+topicな感じの内容。
$ git diff diff --cc index.html index b21ded8,3a98e93..0000000 --- a/index.html +++ b/index.html @@@ -1,9 -1,9 +1,9 @@@ <html><body> <h1>test</h1> - <div class="welcome"> - <div id="hello"> ++ <div id="hello" class="welcome"> <p> - こんぬつわ + こんにちは </p> </div>
コミット
$ git add index.html $ git commit Recorded resolution for 'index.html'. [topic 5ad8548] Merge branch 'master' into topic
rerereが衝突の解消方法を記録したようです。
とりあえずマージが成功したので、これでmasterブランチの最新+topicブランチの状態で、テストが出来ます。
テストが良好なら、安心して続きを出来るというもの。
が、ログを見てみると、さっきのマージでマージコミットが出来ています。
$ git log --oneline 5ad8548 Merge branch 'master' into topic 9e2e252 "こんにちは"に変更。class追加 f0631e9 divタグにidを追加 9dceaee initial
このままハックを続けて、
bbcd955 ハック続き 5ad8548 Merge branch 'master' into topic 9e2e252 "こんにちは"に変更。class追加 f0631e9 divタグにidを追加 9dceaee initial
こんな風に続きをやると、このブランチからマージした時に、このマージコミットもおまけで付いてきてしまいます。1つだけならまだしも、この調子で何回もマージテストを重ねるとかなり沢山のマージコミットが出来てしまい、Linusならぜったい取り込んでくれないということに。
なので、さらに続きをやるのであれば、さっきのマージはいったん取り消します。
$ git reset --hard HEAD^ HEAD is now at 9e2e252 "こんにちは"に変更。class追加 $ git log --oneline 9e2e252 "こんにちは"に変更。class追加 9dceaee initial
マージする前に戻りました。
戻った状態で、何か続きをやります。
diff --git a/index.html b/index.html index b21ded8..0404d83 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,9 @@ <p> こんにちは </p> + <p> + ようこそ! + </p> </div>
コミットしてみます
$ git commit -a -m 'ようこそ 追加' [topic 23d83ed] ようこそ 追加 1 files changed, 3 insertions(+), 0 deletions(-) $ git log --oneline 23d83ed ようこそ 追加 9e2e252 "こんにちは"に変更。class追加 9dceaee initial
コミットしたので、再度masterブランチをマージしてテストしたい。
$ git merge master Auto-merging index.html CONFLICT (content): Merge conflict in index.html Resolved 'index.html' using previous resolution. Automatic merge failed; fix conflicts and then commit the result.
さっきと同じようにコンフリクトしたけれど、、、
$ git diff diff --cc index.html index 0404d83,3a98e93..0000000 --- a/index.html +++ b/index.html @@@ -1,12 -1,9 +1,12 @@@ <html><body> <h1>test</h1> - <div class="welcome"> - <div id="hello"> ++ <div id="hello" class="welcome"> <p> - こんぬつわ + こんにちは + </p> + <p> + ようこそ! </p> </div>
rerereが発動して、さっきの手動マージが再現されている! rerere恐るべし。
一応さらっとdiffを確認したら、コミットするだけ。
$ git add index.html $ git commit [topic bd9e014] Merge branch 'master' into topic
さらにまた続きをしたいなら、resetでマージを取り消して…以下繰り返し、とすれば、テストのためのマージコミットは残さないで済むと。
rerereってけっこう以前からあったみたいです。知らなかった。
stashみたいにrerereのリスト見たりとかpopとかpushとかapplyとか出来たらもっと便利な気がするなぁ。