ブログに戻る

git revert vs reset:履歴を救うのはどちらのコマンドか

2026年9月15日

TL;DR: git revertは古いコミットを打ち消す新しいコミットを追加します。履歴は保存され、他人がpull済みのブランチでも安全。git resetはブランチポインタを後ろへ動かします。履歴は書き換わり、pushしていないコミットにだけ安全。共有ブランチではrevert、ローカルの後片付けではreset。reset --hardはさらにコミット前の作業ツリーの変更も削除します。本物の仕事を食うのはこいつです。

git revertとgit resetの違いは何か?

どちらも「過去のコミットが無かったこと」にできます。違うのはその方法です:

  • git revert <sha> はコミットの逆パッチを計算してコミットします。履歴は「あれを取り消す」1コミットぶん伸びる。悪いコミットはログに残り、その後に取消しが続きます。他のすべてのSHAは無傷です。
  • git reset <sha> は現在のブランチのポインタを<sha>へ動かします。それ以降のコミットは切り離されます。ガベージコレクションまではオブジェクトDBに残りますが、どのブランチからもgit logからも到達できません。

一方は何も書き換えず、もう一方は何事も無かったかのように振る舞います。この性質ひとつで、状況ごとにどちらを使うべきかが決まります:

# デモ:そのまま実行できる使い捨てリポジトリ
git init /tmp/revert-vs-reset && cd /tmp/revert-vs-reset
echo one > file.txt && git add . && git commit -m "one"
echo two > file.txt && git add . && git commit -m "two"

# Revert:履歴には両方のコミットが残り、"two"を打ち消す3つ目が追加される
git revert --no-edit HEAD
git log --oneline        # 3コミット:revert、two、one

# Reset:ブランチポインタが戻り、"two"はログから消える
git reset --hard HEAD~1
git log --oneline        # 1コミット:one

両方実行してgit logを見てください。この非対称性が答えのすべてです。

git resetの—soft、—mixed、—hardは実際何をするのか?

3つのモードは、ポインタが動いたあとに変更がどこに残るかを制御します:

モードブランチポインタステージング作業ツリー編集内容
--soft戻るステージ済みのまま無傷完全に保持
--mixed(デフォルト)戻る未ステージ無傷保持(未ステージ)
--hard戻るリセットリセット削除

表の読み方を具体で:

  • git reset --soft HEAD~1 — 「コミットが早すぎた」。すべてがステージ済みに戻り、再コミット待ち。追加の作業とまとめ直すのも自由です。
  • git reset HEAD~1(mixed)— 「ステージするファイルを間違えた」。編集は作業ツリーに残り、ステージは空。行き場の無いファイルも消したいなら、git remove untracked filesと併用します。
  • git reset --hard HEAD~1 — 「このコミットもコミット前の編集もゴミだ」。gitは二度と尋ねません。stashするか先にコミットでもしない限り、作業ツリー部分を元に戻す手立ては存在しません。

revertにモードが無いのは、何も破壊しないからです。やることは相殺コミットの追加だけ。

git resetの代わりにgit revertを使うべきときは?

たった1つの質問です。このコミットを他人がpull済みか? Yesなら、resetは選択肢から消えます。

他人が作業の土台にしたブランチをresetすると、共有履歴が書き換わります。相手の次のpullは分岐したブランチに直面し、「修正」はたいていforce-pushになり、あなたのミスが全員の問題に変わります。revertは通常のコミットを追加するだけなので、素のgit pullが全員できれいにマージされます。mainやGitHubのプルリクエストでrevertがデフォルトの答えなのはこのためです。「Revert」ボタンが存在するのは、マージ済みPRの履歴を書き換える選択肢が無いからに他なりません。

resetは共有の窓のための道具です。30秒前に作ったコミット、ステージのミス、ローカルの実験ブランチ。作品の唯一のコピーが自分のマシン上にあるなら、履歴を組み替えるのは自由。pre-pushの窓の存在意義はそれに尽きます。

中間ケースもあります。自分だけのフィーチャーブランチにpushしていて、誰もその上に作っていない場合。reset後のforce-pushはそこでは社会的に許容されますが、それでもrevertの方が調整コストは安い。force-pushは自分だけが所有するブランチに取っておきましょう。

共有ブランチではgit revertの方が安全なのか?

はい。慣習ではなく、構造的に安全です:

  • revertは通常のコミットを1つ生むだけ。CIはその上で回り、diffはレビュー可能で、git logは未来の自分に「なぜこの変更が消えたのか」を説明してくれます。
  • resetは中間状態を黙って捨てます。あとからレビューする人は何がいつ取り消されたのか見えません。ログは、それが最初から無かったかのような一直線になるだけです。

実務上の危うい角が2つ:

  • マージコミットのrevertにはgit revert -m 1 <sha>が必要です(最初の親の系列を残す)。-m無しではgitは拒否し、あとはエラーメッセージを読むしかありません。
  • revertはタイムマシンではありません。取り消すのは1コミットのdiffだけです。後続のコミットが同じ行に触れていれば、コンフリクトの解消が発生します。これは「取り消しが絡み合っている」とgitが教えてくれているのであり、有用な情報であって故障ではありません。

直前のコミット専用の取り消し(変更を残すか捨てるか)の選択肢は、git undo last commit: keep the changesにまとまっています。

git restoreはどこに収まるのか?

git restore(と対をなすgit switch)はgit 2.23で登場しました。名前の多義性でresetが下手にこなしていた仕事を、引き受けたのです:

作業旧来の方法明示的な方法
ファイルの作業ツリー編集を破棄git checkout -- file / git reset --hardgit restore file
ファイルをアンステージgit reset HEAD filegit restore --staged file
ブランチを別のコミットへ移動git reset <sha>git reset <sha>(代替なし——これだけは残る)

つまりモダンな役割分担はこうです。restoreはファイルを直し、resetはブランチを動かし、revertは公開済みコミットを取り消す。サジェストを見ると「git revert vs reset vs restore」が一緒に検索されていますが、もっともな話です。ひとつのメンタルモデルが3コマンドに分かれているだけなのですから。旧来のgit checkout形式はどこでもまだ動きます。新しい形式の効能は、ファイルを1つアンステージしたかっただけなのにブランチ丸ごとシュレッダーにかける事故を防ぐことにあります。

悪いコミットを消すのではなく良いコミットを前へコピーしたいなら、それはcherry-pickの仕事です。git cherry-pick: multiple commits, branches, conflictsを参照。

判断のまとめ

  1. コミットは公開済み(push済み、他人がpull済み)→ git revert <sha>
  2. コミットはローカルのみ、やり直したい → git reset --soft--mixedで再コミット。
  3. ローカルのみ、未コミットの汚れごと消したい → git reset --hard。その前に、作業ツリーに他に何があるか正直に一度見る。
  4. 1ファイルだけ壊れた → git restore <file>。ブランチは触れない。

このリストで本当に危険なのは--hardだけです。他はすべてgit reflogで口を割らせられます。gitでの恒久的な喪失は狭い範囲に限られ、たいてい明示的にタイプすることを要求します。身につけるべき習慣は、--hardの前に半秒止まること。gitの鋭い道具の使用を避けることではありません。

— mrsaynothing

Get the next one by email

One email per post. No spam, no algorithms.

self-hosted · no third parties · one-click unsubscribe

what is this?

systemdサービスが起動しない?原因と直し方

記事を楽しんでいただけましたか?私の本業はこのような構築です。 採用のご相談