Edited at

git-rerere [clear|forget <pathspec>|diff|remaining|status|gc]

More than 5 years have passed since last update.

使い方がいまいちよくわからないのでメモです。


git-rerere とは

以下大変参考になります。ありがとうございます。

さて、以下では、「それ」とか「もの」とかが出て来ますけれども、大概が「マージでコンフリクトしたのの解決」のことを指していると思って下さい。


サブコマンド

サブコマンドと git-rerere と関係しそうな別のコマンドを挙げてみます。 自分なりの一言コメントを寄せます。

git rerere clear

: git-rerere が使用するメタデータを消去します。 これ以降に git rerere コマンドを実行したとしてもそれが記録されなくなります。 (git rerere コマンドは、特に陽に実行するものでなくって git が内部で呼び出すことがあることも多少気にする必要があるような気がします。)

git rerere forget <pathspec>

: 記録されていたものを消去します。 以前にやったものを今からやるものへと新しくする際に使用できます。

git rerere status

: どのファイルについてが記録されるかを表示します。

git rerere remaining

: どのファイルが git-rerere で自動的に解消されなかったのかを表示します。 衝突するものの、彼らは消し、私らは編集した、なんていうファイルがある場合にそれも表示されます。

git rerere gc

: ゴミ拾い。

git rerere diff

: 衝突した状態からそれをどう解消したかの差分を表示します。 衝突マーカが残っていないよねという確認にも使用できると思います。

git merge --[no-]rerere-autoupdate

: インデックスへ自動的に反映するかどうかを指定します。 マージ時に git-rerere によって自動的にコンフリクトが解消された場合に、それをインデックスへ即反映するかどうかを指定します。--no(デフォルト)の場合には、ワークツリーへと反映され、インデックスへは反映されません。わかりきっている場合には --no を止めてもよいのかもしれません。

git checkout [--merge|--conflict <style>]

: もう一度ワークツリーへとコンフリクトした状態をチェックアウトします。 コンフリクトした場合に、 git-rerere 等と組み合わせることでコンフリクトの解消の試行錯誤ができると思います。


例をあげながら

かなり長いので、退屈かも…ごめんなさい m(__)m

まず、a というファイルがコンフリクトする例えば以下のようなヒストリがあったとします。

            A---B tmp (branch)

/
---C---D---E master (master)
(original)

NOTE: 括弧の中が a というファイルの中のコンテンツです。


git rerere clear でメタデータをクリアします。

例えば、

% git checkout HEAD^0

% git reset --hard master
HEAD is now at 20e3dc7 master
% git merge tmp
CONFLICT (modify/delete): b deleted in tmp and modified in HEAD. Version HEAD of b left in tree.
Auto-merging a
CONFLICT (content): Merge conflict in a
Recorded preimage for 'a'
Automatic merge failed; fix conflicts and then commit the result.
% cat <<EOT >|a ;# ここでは全部含むように解消してしまいます。
master
original
branch
EOT
% git add .

ここでコミットすると、この衝突の解消が記録されて次回のマージで自動的に衝突が解消されます。ここではその前に git rerere clear しちゃってみます。

% git rerere clear

% git rerere ;# git commit 時に実行されたことになりますけれども陽に実行します
% git commit -m merge
[detached HEAD 6a3179b] Merge branch 'tmp' into HEAD
% git reset --hard master ;# マージの前へ
HEAD is now at 20e3dc7 master
% git merge tmp
CONFLICT (modify/delete): b deleted in tmp and modified in HEAD. Version HEAD of b left in tree.
Auto-merging a
CONFLICT (content): Merge conflict in a
Recorded preimage for 'a'
Automatic merge failed; fix conflicts and then commit the result.
% git ls-files -u -- a
100644 4b48deed3a433909bfd6b6ab3d4b91348b6af464 1 a
100644 1f7391f92b6a3792204e07e99f71f643cc35e7e1 2 a
100644 80858c1ab821392fd59a897dbaedd7d07e1ac403 3 a
% cat a
<<<<<<< HEAD
master
||||||| merged common ancestors
original
=======
branch
>>>>>>> tmp

git rerere clear をしていたためにその後の git rerere で衝突の解消が記録されていなかったため、ワークツリーは再度衝突したままになりました。


git rerere forget <pathspec> では記録されていたものを消去します。

例えば、

% git checkout HEAD^0

% git reset --hard master
HEAD is now at 20e3dc7 master
% git merge --log tmp
CONFLICT (modify/delete): b deleted in tmp and modified in HEAD. Version HEAD of b left in tree.
Auto-merging a
CONFLICT (content): Merge conflict in a
Recorded preimage for 'a'
Automatic merge failed; fix conflicts and then commit the result.
% cat <<EOT >|a ;# さっきと同様に全部含むように解消してしまいます。
master
original
branch
EOT
% git add .
% git commit -m merge
Recorded resolution for 'a'.
[detached HEAD 6382c0d] Merge branch 'tmp' into HEAD
% cat a
master
original
branch

ここで、「あっ間違えた!“original”は必要ないんだった、やり直したい」というような時、単にそのまま編集したものをコミットしたとしても、もう一度マージすると、この今の状態になっちゃう。

% git reset --hard HEAD^ ;# 直前のマージをやり直してそれを記録したい

HEAD is now at 20e3dc7 master
% git merge tmp
CONFLICT (modify/delete): b deleted in tmp and modified in HEAD. Version HEAD of b left in tree.
Auto-merging a
CONFLICT (content): Merge conflict in a
Resolved 'a' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
% cat a
master
original
branch
% cat <<EOT >|a ;# 今度は、“original”を消します。
master
branch
EOT

“original”を消してコミットすれば、次のマージの時には(“original”を消した)今の状態になってくれると期待するのだけれども実際にやってみると…

% git add .

% git commit -m merge
[detached HEAD 82fb342] merge
% git reset --hard HEAD^ ;# 直前のマージで記録されたものになっているかの確認。
% git merge tmp
CONFLICT (modify/delete): b deleted in tmp and modified in HEAD. Version HEAD of b left in tree.
Auto-merging a
CONFLICT (content): Merge conflict in a
Resolved 'a' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
% cat a
master
original
branch

「あれ?これは“original”を消す前のものでワークツリーが更新されている。」 よ〜く見ると、“original”を消した時のマージコミット時には、“Recorded…”の出力がされていないのに気付くかもしれません。 この場合には、一旦 git rerere forget する必要があって、そうしないとその解消は記録されないよということになるみたい。

% git rerere forget -- a

Updated preimage for 'a'
Forgot resolution for a
% cat <<EOT >|a
master
branch
EOT
% git add .
% git commit -m merge
Recorded resolution for 'a'.
[detached HEAD ff04cc9] merge
% git reset --hard HEAD^
HEAD is now at 20e3dc7 master
% git merge tmp
CONFLICT (modify/delete): b deleted in tmp and modified in HEAD. Version HEAD of b left in tree.
Auto-merging a
CONFLICT (content): Merge conflict in a
Resolved 'a' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
% cat a
master
branch

新たに記録したので、今度は“original”を消した状態になりました。


git checkout [--merge|--conflict <style>] でもう一度ワークツリーへとコンフリクトした状態をチェックアウトします。

(順番が前後しちゃうんだけれども…)

直前のやつでは“original”を消すだけの場合だったので、 git rerere が更新してくれていたワークツリーのその状態から編集しちゃうことができました。そうではなくってコンフリクトしたそのままの状態へワークツリーを更新したい場合に使うことができます。

さっきの続きです、

% git ls-files -u -- a

100644 4b48deed3a433909bfd6b6ab3d4b91348b6af464 1 a
100644 1f7391f92b6a3792204e07e99f71f643cc35e7e1 2 a
100644 80858c1ab821392fd59a897dbaedd7d07e1ac403 3 a
% cat a
master
branch

ファイル a を、コンフリクトマーカが付いている状態へ更新したい。ようするに、 git-rerere のおかげで、ワークツリーは衝突が解消された状態へと更新されていてくれてるんだけれども、あるファイルについてはそれは無かったことにしたい、というような場合なんかが考えられると思います。

% git checkout -m -- a

% cat a
<<<<<<< ours
master
||||||| base
original
=======
branch
>>>>>>> theirs

これで一から衝突の解消をすることができます。 「あっ待って!今度はその逆で、 git-rerere がやってくれていた状態へもどしたい!」なんて時には、そのまま git rerere を実行すれば…

% git rerere

Resolved 'a' using previous resolution.
% cat a
master
branch

先程の状態へとワークツリーを更新してくれます。助かります!行ったり来たりすることが出来るようになりました。


git rerere status でどのファイルについてが記録されるかを表示します。

これは、一見そのまんまなんだけれども、じゃあ remaining との違いはなんなんだろう?となっちゃった。というわけで次の git rerere remaining へ、


git rerere remaining でどのファイルが git-rerere で自動的に解消されなかったのかを表示します。

git rerere statusgit rerere remaining では何が違うのかよくわからなかったので、本家に当たります。


commit ac49f5ca84d82e5b10bc1eb022dfdd9b0e8f7749

Author: Martin von Zweigbergk <martin.von.zweigbergkatgmail.com>
Date: Wed Feb 16 05:47:44 2011 -0500

After "rerere" resolves conflicts by reusing old resolution, there would be three kinds of paths with conflict in the index:


  • paths that have been resolved in the working tree by rerere;


  • paths that need further work whose resolution could be recorded;


  • paths that need resolving that rerere won’t help.


When the user wants a list of paths that need hand-resolving, output from "rerere status" does not help, as it shows only the second category, but the paths in the third category still needs work (rerere only makes sense for regular files that have both our side and their side, and does not help other kinds of conflicts, e.g. "we modified, they deleted").

The new subcommand "rerere remaining" can be used to show both. As opposed to "rerere status", this subcommand also skips printing paths that have been added to the index, since these paths are already resolved and are no longer "remaining".

http://git.kernel.org/cgit/git/git.git/commit/?id=ac49f5ca84d82e5b10bc1eb022dfdd9b0e8f7749 rerere "remaining"


これまでの例では b というファイルも出て来ていたんだけれどもそれがこれです。 私らは変更したんだけれども、彼らは消しちゃった、というやつ。

% git ls-files -u  -- a ;# さっきも出てきていたけれども、もう一度。

100644 4b48deed3a433909bfd6b6ab3d4b91348b6af464 1 a
100644 1f7391f92b6a3792204e07e99f71f643cc35e7e1 2 a
100644 80858c1ab821392fd59a897dbaedd7d07e1ac403 3 a
% git ls-files -u -- b;
100644 0776778bf6f543f7e45b0c70afed2c1acf5c1301 1 b
100644 dc3ca4e0fde32adada60cae2c726e5356979bcd2 2 b
% git show :1:b
original-b
% git show :2:b
master-b
% git show :3:b
fatal: Path 'b' is in the index, but not at stage 3.
Did you mean ':1:b'?

「彼ら」つまりブランチ tmp では b は消されているので、インデックスのステージ3へは読み込まれていません。この時に違いが出てきます。

% git rerere forget -- a ;# 一旦両方とも forget

Updated preimage for 'a'
Forgot resolution for a
% git rerere forget -- b
% git rerere status
a
% git rerere remaining
a
b
% git add -- a
% git rerere status
a
% git rerere remaining
b


git rerere diff で衝突した状態からそれをどう解消したかの差分を表示します。

例えば先程の読き、

% git rerere diff

--- a/a
+++ b/a
@@ -1,5 +1,2 @@
-<<<<<<<
-branch
-=======
master
->>>>>>>
+branch

コンフリクトしている状態との差分なので、ちょっと面喰らっちゃいます (^^;


git merge --[no-]rerere-autoupdate でインデックスへ自動的に反映するかどうかを指定します。

さらに先程の続き。

% git rerere ;# さっき forget したので記録します。

Recorded resolution for 'a'.
% git merge --abort
% git merge tmp ;# まず --rerere-autoupdate 無しで
CONFLICT (modify/delete): b deleted in tmp and modified in HEAD. Version HEAD of b left in tree.
Auto-merging a
CONFLICT (content): Merge conflict in a
Resolved 'a' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
% git status
# Not currently on any branch.
# You have unmerged paths.
# (fix conflicts and run "git commit")
#
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: a
# deleted by them: b
#
no changes added to commit (use "git add" and/or "git commit -a")
% git merge --abort
% git merge --rerere-autoupdate tmp ;# 今度はオプション付加
CONFLICT (modify/delete): b deleted in tmp and modified in HEAD. Version HEAD of b left in tree.
Auto-merging a
CONFLICT (content): Merge conflict in a
Staged 'a' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
% git status
# Not currently on any branch.
# You have unmerged paths.
# (fix conflicts and run "git commit")
#
# Changes to be committed:
#
# modified: a
#
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# deleted by them: b
#

長かったね…おつかれ様でした!