gitでマージ作業を中止して元の状態に戻す


2011年 01月 20日

問題

gitでは空気を吸うようにブランチを作り空気を吐くようにマージを行います。

例えば新機能Xを実装する場合、

  1. X用のトピックブランチを作成し、
  2. 実装を進めて、
  3. 完成したら統合ブランチへマージする

というのが普通です。
具体的にコマンド例を挙げると以下のようなものになります:

$ git checkout -b topic-x master
Switched to a new branch 'topic-x'

$ $EDITOR

$ git add foo.x bar.x baz.x

$ git commit -m 'Implement X'
[topic-x 0000001] Implement X
 3 files changed, 8 insertions(+), 5 deletions(-)

$ $EDITOR

$ git add qux.x

$ git commit -m 'Fix a typo'
[topic-x 0000002] Fix a typo
 1 files changed, 7 insertions(+), 5 deletions(-)

$ git checkout master
Switched to branch 'master'

$ git merge topic-x
Updating 0000000..0000002
Fast-forward
 foo.x |    5 ++++-
 bar.x |    4 ++--
 baz.x |    4 ++--
 qux.x |   12 +++++++-----
 3 files changed, 15 insertions(+), 10 deletions(-)

さてマージが完了したは良いものの、
よくよくログを見返してみると topic-x の内容に誤りがあり、
実はまだ完成とは言えない状態だということに気付いたとしましょう。
topic-x で修正を積み重ねてから再度masterへマージしてもよいのですが、
この作業はまだ外部へ公開(push)していないので、
できれば外面を綺麗に保つために一旦マージをなかったことにしてから
修正を行いたいものです。
しかしどうすればよいでしょうか。

解決方法

これまでに何度か登場してきたgit resetを使います。

git reset --hard $commit_id で現在のブランチの指す先を差し替えられますから、
git log --graph --oneline --decorate などでコミットログを表示して
マージ前の状態に相当するコミット(例えばID badcafe のコミット)を探して
git reset --hard badcafe とすればマージ前の状態に巻き戻すことができます。
でもこれはちょっと面倒です。

実は git merge のような「危険」な(= 現在のブランチの内容を大幅に変える可能性のある)コマンドの場合、
実行前の状態を ORIG_HEAD という名前で参照できるようになっています。
つまり、わざわざコミットログを確認しなくても以下のコマンドで
マージ前の状態に巻き戻すことができます:

$ git reset --hard ORIG_HEAD

これで体裁を繕うことができるようになりました。
やりましたね。

補足

今回の例ではマージによるコンフリクトは発生しませんでしたが、
コンフリクトを手動解決している最中に一旦マージ前の状態に巻き戻したくなった場合にも
git reset --hard ORIG_HEAD
は使えます。

git merge 以外にも「危険」なコマンドはいくつかあります。
マージ後に色々作業してからでは ORIG_HEAD の指すコミットが
マージ前の状態のものであるとは限りませんから、
マージ結果を巻き戻したい場合は余計なことをせず早目に
git reset --hard ORIG_HEAD
とした方がよいです。

また、現在ではreflogという機構があるため、
色々作業してしまった後でも ORIG_HEAD の代わりに
HEAD@{1} などとして過去の状態を参照することができます。

(続く)