はじめに
「間違ったコミットを消したい」
そう思ったときに git reset と git revert のどちらを使えばいいか迷った経験はありませんか。
なんとなくコピペしたコマンドで解決できたとしても、仕組みを理解していないと、共有ブランチで履歴を壊してしまうといった事故につながります。
この記事では、resetとrevertの根本的な違いを図解を交えて整理し、実務で使う際にどのように使い分ければいいのかを考えます。
前提知識
resetとrevertの違いを理解するには、まずGitが管理している3つの領域を知っておく必要があります。
- ワーキングツリー 実際にファイルを編集している作業ディレクトリ
- インデックス(ステージングエリア) 次のコミットに含める変更を一時的に置いておく場所
- コミット履歴 これまでに記録されたコミットの積み重ね
git reset はオプションによって、この3つの領域のうちどこまでを書き換えるかが変わります。
一方 git revert は、コミット履歴に新しいコミットを追加するだけで、過去の履歴そのものには手を加えません。
この違いこそが、resetとrevertを使い分ける最大のポイントです。
git resetとは
git reset は、現在のブランチの先端(HEAD)を指定したコミットに移動させるコマンドです。指定したモードによって、ワーキングツリーとインデックスの状態も変わります。
主なモードは次の3つです。
--soft
コミット履歴だけを巻き戻し、ワーキングツリーとインデックスの内容はそのまま残します。直前のコミットをやり直したいときによく使われます。
git reset --soft HEAD~1
このコマンドを実行すると、直前のコミットが取り消され、その変更内容はステージング済みの状態としてインデックスに残ります。
--mixed(オプション省略時のデフォルト)
コミット履歴とインデックスを巻き戻し、ワーキングツリーの内容だけを残します。コミットは取り消したいけれど、変更内容自体は手元に残して見直したいときに向いています。
git reset --mixed HEAD~1
--hard
コミット履歴、インデックス、ワーキングツリーのすべてを指定したコミットの状態に戻します。作業中の変更も含めて完全に消えるため、実行前には必ず git status で現在の状態を確認してください。
git reset --hard HEAD~1
一度実行すると、作業ツリーの変更は失われますが、コミット済みの内容はreflogから復旧できる場合があります。
ただし git reflog を使えば、直前までのHEADの移動履歴を確認できる場合があるため、誤操作に気づいたらすぐに git reflog を確認する習慣をつけておくと安心です。
git revertとは
git revert は、指定したコミットの変更内容を打ち消す、新しいコミットを作成するコマンドです。元のコミットは履歴から消えず、そのまま残ります。
git revert <コミットハッシュ>
このコマンドを実行すると、指定したコミットとは逆方向の差分を適用した新しいコミットが1つ作られます。
履歴を改変しないため、すでにリモートリポジトリへpushして他のメンバーと共有しているコミットを取り消したいときに適しています。
なお、作業中の変更が残っている状態でrevertを実行するとエラーになることがあります。その場合は先にコミットするか、git stash で変更を一時退避させてから実行してください。
複数のコミットをまとめて打ち消したい場合は範囲指定も可能です。
git revert --no-edit <古いコミット>..<新しいコミット>
--no-edit を付けると、コミットメッセージの編集画面を都度開かずに済むため、打ち消すコミット数が多いときに便利です。
図解で比較するresetとrevertの動き
共有ブランチのコミット履歴が「A→B→C→D」とあり、Cで誤ってバグを混入させてしまったという例で、それぞれの操作後に履歴がどう変化するかを見てみます。
操作前の状態
git reset --hard B を実行した場合
バグが混入する前の状態に完全に戻すために、reset --hardを実行します。
この場合、CとDのコミットが履歴そのものから消え、HEADはBまで巻き戻ります。
git revert C を実行した場合
AからDまでの履歴はそのまま残り、Cの変更内容だけを打ち消す新しいコミットが末尾に追加されます。過去に何が起きたかを消さずに、正しい状態へ戻せる点がポイントです。
resetとrevertの違い一覧
両者の違いを一言でまとめると、履歴を書き換えるか、書き換えないかという点にあります。
| 項目 | git reset | git revert |
|---|---|---|
| コミット履歴 | 書き換わる(過去のコミットが消える) | 書き換わらない(新しいコミットが追加される) |
| 取り消した記録 | 残らない | 新しいコミットとして残る |
| リモートへの反映 | 強制push(force push)が必要になる場合が多い | 通常のpushで反映できる |
| 主な用途 | ローカルブランチや個人作業の整理 | 共有ブランチ、公開済みの変更の取り消し |
| 危険度 | 高い(--hardは変更が失われる) | 比較的低い |
特に注意したいのが、リモートへの反映方法の違いです。
resetでコミットを過去に巻き戻した状態をpushしようとすると、リモート側の履歴より古い状態になるため、通常のpushでは拒否されます。
無理に反映させるには force push(git push --force)が必要になりますが、これは他のメンバーが同じブランチで作業していた場合、その変更を消してしまう危険な操作です。
一方revertは、履歴に新しいコミットを積み上げるだけなので、通常のpushでそのまま反映できます。
どっちを使うのかは状況による
判断に迷ったときは、次の基準で考えると分かりやすいです。
- そのコミットはすでにリモートへpushされていて、他の人と共有されているか
- されている場合は revert を使う
- まだ自分のローカルにしかない場合は reset を使ってよい
- 履歴をきれいに整理したいだけか、それとも変更内容そのものを打ち消したいか
- 整理目的(コミットの統合など)は reset --soft が向いている
- 変更を打ち消す記録も残したい場合は revert が向いている
チーム開発においては、共有ブランチ(develop、mainなど)へのresetは基本的に避け、どうしても必要な場合はチーム全員に周知したうえで慎重に行います。
実際、共有ブランチでresetを使ったことで、他のメンバーのローカル環境と履歴の食い違いが発生し、混乱を招くケースは少なくありません。
まとめ
- git resetはHEADの位置を移動させ、履歴そのものを書き換えるコマンドです
- git revertは過去のコミットを打ち消す新しいコミットを作成し、履歴は書き換えません
- ローカルでの作業整理にはreset、共有ブランチでの変更取り消しにはrevertを使うのが基本方針です
- --hardオプションや共有ブランチでのforce pushは特に注意して扱ってください
resetとrevertはどちらも便利なコマンドですが、仕組みを理解したうえで使い分けることで、Git操作に対する不安がぐっと減るはずです。