TL;DR:直前のコミットを取り消して変更を残すなら git reset --soft HEAD~1(変更はステージされたまま)、git reset HEAD~1(変更はワーキングツリーに残ります)。すでにpush済みのコミットなら代わりに git revert HEAD を実行しましょう。履歴を書き換えず、取り消し内容を新しいコミットとして作ります。 この3つのコマンドで「コミットが早すぎた」場面のほとんどをカバーできます。以降では各ケースをコピペできるコマンドとともに順に説明し、--soft、--mixed、--hard の違いを解説し、失敗したときの復旧方法も示します。以下のコマンドは、Linux、macOS、Windowsのいずれの最近のGitでも動作します。
直前のコミットを取り消しつつ、変更は残すには?
いちばんよくある状況です。コミットした直後にtypoを見つけた、ファイルを入れ忘れた、そもそも別のコミットに分けるべきだと気づいた。まだ何もpushしていない。コミットを取り消して、すべて元の場所に戻しましょう:
# コミットはどこにも残らない — 変更はステージングエリアに戻る
git reset --soft HEAD~1
# 変更をワーキングツリーに戻す(アンステージ状態)ならこちら
git reset HEAD~1 HEAD~1 は「HEADが今指している場所より1つ前のコミット」です。どちらのコマンドでもファイルはディスク上で無傷のまま。動くのはブランチポインタだけです。git status で確認できます。--soft なら変更は ステージ済み で、修正を混ぜて再コミットする態勢です。フラグがなければ変更は アンステージ なので、まず自由に編集できます。
直前のコミットにファイルを追加したいだけなら、取り消さないほうが安全な習慣があります:
git add forgotten-file.txt
git commit --amend --no-edit --amend は直前のコミットをその場で置き換えます(ここではメッセージは変更していません)。なお、 push済みの コミットをamendすると履歴を書き換えることになります。これについては後述します。
—soft、—mixed、—hardの違いは?
ここだけは暗記する価値があります。フラグが変更の行き先を決め、失われるかどうかも決めるからです:
| フラグ | コミットは取り消される? | ディスク上の変更 | ステージされる? | 典型的な用途 |
|---|---|---|---|---|
--soft | はい | 残る | はい | 小さな修正を加えて再コミット |
--mixed(デフォルト) | はい | 残る | いいえ | 変更を組み直し、選んでステージし直す |
--hard | はい | 削除 | — | 作業を完全に捨てる |
git revert | いいえ(新しいコミット) | 残る | — | push済みコミットの取り消し |
危険なのは --hard だけです。コミット と 変更の両方を捨てます。reset --hard の前には、今あるものをstashするかブランチに退避させてください:
git branch backup-before-reset # 安い保険
git reset --hard HEAD~1 # コミット + 変更が消える すでに危険なほうを実行してしまっても、まだ終わりではありません。git reflog はHEADが辿った場所を覚えています:
git reflog # 失ったコミットのハッシュを探す
git reset --hard HEAD@{1} # または: git reset --hard <hash> reflogはぶら下がったコミットをデフォルトで約90日間保持します。「間違えてhard-resetした」は、ガベージコレクションより先に手を動かせばほぼ常に復旧可能だということです。
すでにpush済みのコミットだったら?
コミットが共有ブランチ(自分のフィーチャーブランチ以外のすべて)に乗っているなら、履歴を書き換えては いけません。git revert を使いましょう。逆の変更を計算してコミットしてくれます:
git revert HEAD
git push pullした人は全員、古いコミットの変更を打ち消す新しいコミットを受け取るだけです。force-pushもなければ、壊れる同僚もいません。連続したコミットを取り消すなら、範囲でrevertします:git revert --no-commit HEAD~3..HEAD && git commit。
代替手段の git reset --hard HEAD~1 && git push --force-with-lease は、誰も土台にしていないブランチでしか許容されません。そして --force-with-lease(素の --force は決して使わない)だけが唯一の安全な形です。誰かが間にpushしていたら上書きを拒否してくれます。共有ブランチへのforce-pushは、チームがコミットを失い、CIのログが誰のチェックアウトとも謎に一致しなくなる原因です。
直前のコミットを取り消しつつ、あとで使えるよう残しておくには?
コミット自体は良い仕事なのに、住所が間違っていることがあります。間違ったブランチにあるか、早すぎたか。取り消す代わりに、引っ越しさせましょう:
git branch stash-commit # コミットを新しいブランチに退避
git reset --hard HEAD~1 # それから現在のブランチをきれいにする 現在のブランチに触れずに、そのコミットだけ別のブランチへ持っていくなら:
git cherry-pick <hash> # 移動先のブランチにいる状態で reset --soft、cherry-pick、revert の間に、移動も無効化もできないコミットは存在しません。コツは、フラグに手を伸ばす前に「移動」と「取り消し」を選ぶことです。
どの取り消しを使うべき?判断の早見表
- 未push、修正して再コミットしたい →
git reset --soft HEAD~1 - 未push、選んでステージし直したい →
git reset HEAD~1(mixed) - 変更を完全に消したい →
git reset --hard HEAD~1(後悔してもreflogが覚えています) - すでに共有ブランチへpush済み →
git revert HEAD - コミットは別のブランチにあるべき → 取り消さずに
cherry-pick
最後にひとつ、運用上のヒントを。悪いコミットがサーバーまで届いていたら、デプロイフックの失敗や、force-push後のCIランナーの不調などは、次に見るべき場所がGitではなくマシンのログです。systemdのマシンなら journalctl -u <service> -n 100 で何がいつ実行されたか正確に分かります。journalctlチートシートにコピペ用のパターンを揃えてあります。また、diffレビューにローカルLLMを組み込むなら、主要2ツールをOllama vs LM Studioで比較しています。
— mrsaynothing
Get the next one by email
One email per post. No spam, no algorithms.
what is this?journalctlチートシート:Linuxログの表示、絞り込み、ライブ追跡
記事を楽しんでいただけましたか?私の本業はこのような構築です。 採用のご相談