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とか出来たらもっと便利な気がするなぁ。