※現在、ブログ記事を移行中のため一部表示が崩れる場合がございます。
順次修正対応にあたっておりますので何卒ご了承いただけますよう、お願い致します。

gitで一度行った変更をなかったことにする方法4つ


2011年 02月 20日

gitでは様々な方法でコミットログを書き換えることができます。
その一例として一度行った変更をなかったことにする方法を4つ紹介します。

問題1: ライブラリの新機能を試すためにあれこれ適当なコードを書いてみた。でももう要らない。

$ $EDITOR
$ git commit -am 'foo'
$ $EDITOR
$ git commit -am 'bar'
$ $EDITOR
$ git commit -am 'baz'

のように適当な区切りでコミットして行ったものの、
結局全部要らないからなかったことにしたいということはままあります。

解答1: git reset –hard HEAD~{n}

コミットしたもの全てを歴史から消し去りたい場合は
git reset --hard
を使います。

この例の場合は3回のコミットを全てなかったことにしたいので、
以下のコマンドで消し去ることができます:

$ git reset --hard HEAD~3

HEAD~{n} でn回コミットする前の状態を参照することができます。

問題2: トピックブランチをマージしたけど実はまだ不完全だった。マージをやり直したい。

$ git checkout master
$ git merge topic

のようにマージしたとして

$ git diff ORIG_HEAD

などとして結果を再確認していたら、実はtopicブランチの内容が不完全だったので、
マージをやり直したいということはままあります。

解答2: git reset –hard ORIG_HEAD

問題1の応用です。 git merge の実行直後であればマージ前の状態を ORIG_HEAD で参照できます。

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

問題3: リリース後に発覚したバグ。原因は30日前に自分が行ったコミットだった。なかったことにしたい。

この場合は既にリリースした後なので問題1や問題2のようにほいほい git reset することはできません。
例えば複数人で共同開発をしている場合なら各種変更は既に共有リポジトリへ git push された後ですから、
勝手にコミットログを書き換えて git push -f したら大変な混乱を招きます。
そもそも多数のブランチがあれやこれやとマージされているためにコミットログを書き変えようという気力すら起こらない場合もあります。
何せコミットしたのは30日も昔ですし。

解答3: git revert $commit_id

一口に「なかったことにする」と言っても、
問題3では問題1や問題2のように変更内容を完全に抹消したい訳ではなく、
「変更内容を巻き戻す内容のコミットをする」のが本当にしたいことになります。

この場合は git revert を使います。
例えば30日前に行ったコミットのIDがdeadbeafだった場合、以下のコマンドで「なかったことにする」ことができます:

$ git revert deadbeaf

問題4: 新しいコミットしようとして間違えてgit commit –amendで書き換えてしまった。元に戻したい。

gitで最後に行ったコミットを修正する
方法を知った人が一度はやりそうなミスとして、次のようなものがあります:

$ git commit -am 'Implement X'

$ $EDITOR  # 無駄なコードが残ってたので消した。
$ git commit -am 'Implement X' --amend

$ $EDITOR  # と思ったらコンパイルできない状態だった。
$ git commit -am 'Implement X' --amend

$ $EDITOR  # 変数名を打ち間違えてた。
$ git commit -am 'Implement X' --amend

このように何度も git commit --amend で細かい修正を繰り返した後、
全く新しい機能を実装してコミットする際に、
つい手癖でコマンドラインの履歴を手繰って -m の値だけ書き換えて以下のコマンドを実行してしまうというものです:

$ $EDITOR  # 新機能を実装した。
$ git commit -am 'Implement Y' --amend  # あれ?

git reset --hard HEAD~1 としても既に Implement X 分のコミットは闇に葬られているため意味がありません。
しかもこういう時に限って Implement XImplement Y のコミットの変更範囲が結構な割合で重複しているものです。

解答4: git reset HEAD@{1}

今時のgitにはreflogという便利な機構があり、
例えば git commit --amend する直前の状態は HEAD@{1} で参照することができます。

この例の場合は以下のコマンドでつつがなくコミットをやり直すことができます:

$ git reset HEAD@{1}
$ git commit -am 'Implement Y'

(続く)