gitでは様々な方法でコミットログを書き換えることができます。
その一例として一度行った変更をなかったことにする方法を4つ紹介します。
$ $EDITOR
$ git commit -am 'foo'
$ $EDITOR
$ git commit -am 'bar'
$ $EDITOR
$ git commit -am 'baz'
のように適当な区切りでコミットして行ったものの、
結局全部要らないからなかったことにしたいということはままあります。
コミットしたもの全てを歴史から消し去りたい場合はgit reset --hard
を使います。
この例の場合は3回のコミットを全てなかったことにしたいので、
以下のコマンドで消し去ることができます:
$ git reset --hard HEAD~3
HEAD~{n}
でn回コミットする前の状態を参照することができます。
$ git checkout master
$ git merge topic
のようにマージしたとして
$ git diff ORIG_HEAD
などとして結果を再確認していたら、実はtopicブランチの内容が不完全だったので、
マージをやり直したいということはままあります。
問題1の応用です。 git merge
の実行直後であればマージ前の状態を ORIG_HEAD
で参照できます。
この場合は既にリリースした後なので問題1や問題2のようにほいほい git reset
することはできません。
例えば複数人で共同開発をしている場合なら各種変更は既に共有リポジトリへ git push
された後ですから、
勝手にコミットログを書き換えて git push -f
したら大変な混乱を招きます。
そもそも多数のブランチがあれやこれやとマージされているためにコミットログを書き変えようという気力すら起こらない場合もあります。
何せコミットしたのは30日も昔ですし。
一口に「なかったことにする」と言っても、
問題3では問題1や問題2のように変更内容を完全に抹消したい訳ではなく、
「変更内容を巻き戻す内容のコミットをする」のが本当にしたいことになります。
この場合は git revert
を使います。
例えば30日前に行ったコミットのIDがdeadbeaf
だった場合、以下のコマンドで「なかったことにする」ことができます:
$ git revert deadbeaf
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 X
と Implement Y
のコミットの変更範囲が結構な割合で重複しているものです。
今時のgitにはreflogという便利な機構があり、
例えば git commit --amend
する直前の状態は HEAD@{1}
で参照することができます。
この例の場合は以下のコマンドでつつがなくコミットをやり直すことができます:
$ git reset HEAD@{1}
$ git commit -am 'Implement Y'
(続く)