マージ後のreset HEAD^は危険だった
直前のマージを取り消す場合は、
× git reset --hard HEAD^
ではなく、
○ git reset --hard ORIG_HEAD
としないと危ない、という話。
「マージ後にgit reset --hard HEAD^で取り消し」は去年の日記でもけっこう使ってるけど、たまたま上手くいっていたからよかったが、ORIG_HEADが正しい指定方法だった。場合によってはちょっと危ない。
マージコミットは複数のparentが記録されるが、mergeコマンドによって先端を移動するブランチ(=カレントのブランチ)を1番目の親としてマージコミットが作成される。
例えば topicブランチで git merge master とした場合に作成されるコミットオブジェクトは、1番目のparentはtopicブランチのハッシュ値で、2番目はmasterブランチのハッシュ値となる。
なので、その後topicブランチで git reset --hard HEAD^ とするとマージをする前に戻れる(HEAD^はHEADの1番目の親を指す)。
しかし、これは危険だった。
上記マージ後すぐにmasterブランチに移動してtopicブランチをマージすると、さっきマージしたtopicブランチの先頭に Fast-forwardとなる。新しいマージコミットは作成されず、topicブランチの先頭のマージコミットがそのまま使用されるので、マージ後のHEADの1番目のparentはtopicブランチの以前の先端であり、さっきと同じように git reset --hard HEAD^ とやると、topicブランチのマージ前の状態にresetしてしまう。
実験
masterブランチとtopicブランチで枝分かれしている状態をつくる
$ git init $ echo hehehe > hehehe.txt $ git add hehehe.txt $ git commit -m 'hehehe追加' [master (root-commit) 41cd63b] hehehe追加 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 hehehe.txt $ git checkout -b topic Switched to a new branch 'topic' $ echo fuhihi > fuhihi.txt $ git add fuhihi.txt $ git commit -m 'fuhihi追加' [topic 9f74cf3] fuhihi追加 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 fuhihi.txt $ git log --oneline 9f74cf3 fuhihi追加 41cd63b hehehe追加 $ git checkout master $ echo hoge > hoge.txt $ git add hoge.txt $ git commit -m 'hoge追加' [master 4b20f85] hoge追加 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 hoge.txt $ git log --oneline 4b20f85 hoge追加 41cd63b hehehe追加
とりあえず準備完了
$ git show-branch * [master] hoge追加 ! [topic] fuhihi追加 -- * [master] hoge追加 + [topic] fuhihi追加 *+ [master^] hehehe追加
topicブランチでmasterブランチをマージしてみる
$ git checkout topic Switched to branch 'topic' $ git log --oneline 9f74cf3 fuhihi追加 41cd63b hehehe追加 $ git merge master Merge made by recursive. hoge.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 hoge.txt
作成されたマージコミットの1番目の親が以前の先端「9f74cf3」であることが分かる。
$ git log --oneline --parents c93f737 9f74cf3 4b20f85 Merge branch 'master' into topic 4b20f85 41cd63b hoge追加 9f74cf3 41cd63b fuhihi追加 41cd63b hehehe追加
よって、以下の方法でマージ前の状態に戻れる
$ git reset --hard HEAD^ HEAD is now at 9f74cf3 fuhihi追加
このパターンではHEAD^にresetしても大丈夫だった。
もう一度topicブランチでmasterブランチをマージ
$ git merge master Merge made by recursive. hoge.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 hoge.txt $ git log --oneline --parents acc49c4 9f74cf3 4b20f85 Merge branch 'master' into topic 4b20f85 41cd63b hoge追加 9f74cf3 41cd63b fuhihi追加 41cd63b hehehe追加
次に、masterブランチでtopicブランチをマージしてみる
$ git checkout master Switched to branch 'master' $ git log --oneline --parents 4b20f85 41cd63b hoge追加 41cd63b hehehe追加 $ git merge topic Updating 4b20f85..acc49c4 Fast-forward fuhihi.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 fuhihi.txt
Fast-forward になった。
$ git log --oneline --parents master acc49c4 9f74cf3 4b20f85 Merge branch 'master' into topic 4b20f85 41cd63b hoge追加 9f74cf3 41cd63b fuhihi追加 41cd63b hehehe追加
topicブランチに Fast-forward したということは、、、
$ git log --oneline --parents topic acc49c4 9f74cf3 4b20f85 Merge branch 'master' into topic 4b20f85 41cd63b hoge追加 9f74cf3 41cd63b fuhihi追加 41cd63b hehehe追加
両ブランチとも全く同じ状態になったことが分かる。
もしここで直前のマージを取り消そうとして
git reset --hard HEAD^
とした場合、どうなるか。
$ git log --oneline HEAD^ 9f74cf3 fuhihi追加 41cd63b hehehe追加
topicブランチのマージ前の状態になってしまう。
$ git log --oneline ORIG_HEAD 4b20f85 hoge追加 41cd63b hehehe追加
ORIG_HEADなら大丈夫。