git resetとgit revertの違い・履歴を消すか、打ち消すか
前記事では、行き過ぎた変更をstashで一時的にどける方法を見ました。今回はもう一段階進んで、コミットしてしまった内容そのものを取り消したい場合の話です。取り消し方には大きくgit resetとgit revertの2種類があり、どちらも「なかったことにする」ように見えて、内部でやっていることはまったく違います。
--soft・--mixed・--hardは「どこまで巻き戻すか」の違い
git resetは直前のコミットを取り消す1種類のコマンドだと思うかもしれませんが、実際には--soft・--mixed(何も付けない場合のデフォルト)・--hardの3段階があり、HEADの位置・インデックス(ステージ)・作業ツリーのうち、どこまで巻き戻すかが異なります。
# --soft: HEADだけ戻す。変更内容はステージされたまま残る git reset --soft HEAD^ git commit -a -c ORIG_HEAD # コミットをやり直す、といった使い方 # --mixed(デフォルト): HEADとステージを戻す。作業ツリーの内容は残る git reset HEAD~1 # --hard: HEAD・ステージ・作業ツリーの全部を戻す。変更内容は失われる git reset --hard HEAD~1
引っ越し作業に例えると、--softは「トラックに積む直前、荷造りした段ボールはそのまま残す」巻き戻しです。--mixedは「段ボールを開けて、荷物を部屋に出した状態」に戻します(荷物自体は手元にある)。--hardは「引っ越しの計画自体を白紙に戻し、荷物も元の配置に戻してしまう」巻き戻しです。--hardだけは、開けた段ボールの中身(作業ツリーの変更)ごと消えてしまう点が他の2つと決定的に違います。
共有した履歴を消すな、打ち消すコミットを積め
間違えたコミットを消したいなら、resetで履歴から取り除いてしまえばいいと思うかもしれません。ですがresetは、指定した位置より後ろのコミットを履歴から取り除く(=書き換える)操作です。rebase記事で見たのと同じ理由で、すでにpushして他の人がpull済みのコミットに対してresetを行うと、履歴の食い違いが生じます。共有済みのコミットを取り消したい場合は、resetではなくgit revertを使います。revertは指定したコミットの変更内容を打ち消す、新しいコミットを追加する操作です。履歴そのものは削除せず、「取り消しました」という事実を新しい記録として積み上げます。
# 危険: 共有済みのコミットをresetで消す(他の人と食い違う) git reset --hard HEAD~1 # 安全: 打ち消しコミットを新たに追加する git revert HEAD # 直前のコミットを打ち消す git revert HEAD~2 # 2つ前のコミットだけを打ち消す
経理処理に例えると、resetは「発行済みの領収書そのものを破り捨てる」やり方です。自分の手元では帳簿がきれいになりますが、すでに相手に渡した控えとは記録が食い違ってしまいます。revertは「元の領収書はそのまま残し、それを打ち消す取り消し伝票を新たに発行する」やり方です。帳簿(履歴)を辿れば、何が起きて何を取り消したのかが後から誰にでも追跡できます。
よくあるはまりポイント:--hardで消した変更が「本当に消えている」場合とそうでない場合
git reset --hardで消した変更は、もう二度と手に入らないと思うかもしれません。ですが実際には、消えた対象が「コミット済みだったかどうか」で運命が分かれます。一度でもコミットしていた内容であれば、Gitの内部にはまだそのコミットの実体が残っており、次回以降の記事で扱うreflogという仕組みを使えば復元できる可能性があります。一方、一度もコミットしていなかった作業ツリーやステージ上だけの変更は、--hardの瞬間に本当に失われ、reflogを見ても手がかりは残っていません。「とりあえずコミットしておく」という一手間が、この差を生む最後の保険になります。
- git reset --soft / --mixed / --hard
- HEADの位置・インデックス・作業ツリーのうち、どこまで巻き戻すかが異なる3段階のリセット。--hardのみ作業ツリーの変更も失う
- git revert
- 指定したコミットの変更内容を打ち消す新しいコミットを追加する操作。履歴を削除しないため共有ブランチでも安全
- ORIG_HEAD
- reset実行前のHEAD位置を指す参照。resetをやり直したいときの手がかりになる
resetを実行する前に「このコミットは自分しか見ていないローカルだけの話か、それとも既にpushして誰かがpull済みか」を必ず確認しましょう。前者ならresetで自由に整理してよく、後者ならrevertを使う、という判断基準はrebaseのGolden Ruleと本質的に同じです。
「コミット済みなら復元できる可能性がある」という話が出てきましたが、実際にどうやって過去のコミットを探し出すのか、続くgit reflogで具体的に見ていきます。