ウェブエンジニア問題集

取り消し操作の完全ガイド — reset・revert・restore

「間違ってコミットしてしまった」「変更を戻したい」「ブランチを巻き戻したい」。Gitでもっとも混乱するのがこの取り消し操作です。使うコマンドによって戻る範囲履歴への影響がまったく違います。

この章で3つのコマンドを整理して、必要なときに迷わず使えるようにしましょう。

3つのコマンドの役割

それぞれの役割を一言で表すと以下のようになります。

コマンド役割履歴への影響
git restoreファイルの変更を取り消すなし(作業ディレクトリ/ステージングのみ)
git resetコミットを巻き戻す(履歴から消す)あり(コミットが消える)
git revert指定コミットを打ち消すコミットを作るあり(新しいコミットが追加される)

どれを使うべきかの判断基準はシンプルです。

git restore — 未コミットの変更を取り消す

git restore は、まだコミットしていない変更を元に戻すコマンドです。前章で出てきた git checkout -- file の新しい書き方です。

作業ディレクトリの変更を捨てる

# app.js の編集内容を捨てて、最後のコミット時点に戻す
git restore src/app.js
 
# すべてのファイルの変更を捨てる
git restore .

この操作は変更を完全に破棄するので注意してください。restore を実行した瞬間、編集内容はGitからは復元できなくなります。

ステージングを取り消す

git add しちゃったけど戻したい、というときは --staged オプションを使います。

# app.js のステージングだけ解除(ファイル内容はそのまま)
git restore --staged src/app.js

これはステージング状態を解除するだけで、ファイルの編集内容には影響しません。

整理すると

git restore file           # 作業ディレクトリの変更を捨てる
git restore --staged file  # ステージングを取り消す
git restore --source HEAD~1 file  # 1つ前のコミットの状態にする

git reset — コミットを巻き戻す

git reset は、コミットをなかったことにするコマンドです。ブランチの先端ポインタを過去のコミットに戻します。

3つのモード

reset には3つのモードがあり、取り消す範囲が変わります。

モード履歴ステージング作業ディレクトリ
--soft巻き戻す保持保持
--mixed(デフォルト)巻き戻すリセット保持
--hard巻き戻すリセット全消去

図で見ると以下のようになります。

よく使うパターン

直前のコミットを取り消したい(変更内容は残す)

git reset --soft HEAD~1

ステージング状態に戻るので、コミットメッセージを書き直してやり直せます。

直前のコミットを取り消して、変更を作業ディレクトリに戻す

git reset HEAD~1
# --mixed はデフォルトなので省略可

直前のコミットを完全に破棄する

git reset --hard HEAD~1

--hard編集内容も完全に消えます。一度使うと戻せないので慎重に。

HEAD~1 と HEAD^ の違い

どちらも「1つ前のコミット」を指します。好みで使い分けてOKです。

git reset HEAD~1   # 1つ前
git reset HEAD~3   # 3つ前
git reset HEAD^    # 1つ前(`~1` と同じ)
git reset HEAD^^   # 2つ前

resetの危険性

resetはコミットを履歴から消します。ローカルでまだpushしていないコミットなら問題ありませんが、既にpushしたコミットをresetすると、他人の履歴と食い違いが発生します

rebaseと同じ理由で、共有済みのコミットにresetを使ってはいけません。その場合は次の revert を使います。

git revert — 打ち消すコミットを作る

git revert は、指定したコミットを打ち消す「新しいコミット」を作るコマンドです。履歴は書き換えず、代わりに「あのコミットを取り消す」という意味のコミットを追加します。

git revert abc1234

実行すると、以下のような履歴になります。

Revert C というコミットが追加され、結果として C の変更が打ち消されます。履歴には CRevert C も両方残るので、誰の目にも「いったんこの変更を入れて、後で取り消した」と分かるのが特徴です。

revertのメリット

  • 履歴を書き換えないので、共有ブランチでも安全
  • 取り消した経緯が残るので、監査やレビューに強い
  • 間違えたらさらにrevertすれば戻せる

いつrevertを使うか

  • 既にmainにマージされた変更を取り消したい
  • 本番リリース済みの変更を戻したい
  • チームメンバーと共有しているブランチで過去のコミットを取り消したい

要するに「他の人がそのコミットを持っている可能性がある」なら revert です。

マージコミットをrevertする

マージコミットをrevertするときは少し特殊で、-m オプションでどちらの親を残すか指定します。

git revert -m 1 <マージコミットのハッシ>

1 は「1つ目の親(通常はマージ先のブランチ)を残す」という意味です。これを指定しないとエラーになります。

操作の違いを比較

3つのコマンドの違いをまとめます。

状況restoreresetrevert
ファイル編集を捨てたい×
ステージングを取り消したい×
直前のローカルコミットを消したい×
共有済みのコミットを取り消したい××
履歴を綺麗に保ちたい×
取り消した経緯を残したい××

reflogという最終防衛ライン

reset --hard してしまった、どうしよう」というときの救済策が git reflog です。

reflog は、あなたのHEADが過去にどのコミットを指していたかの記録です。ブランチを切り替えたり、resetしたり、rebaseしたりした履歴がすべて残っています。

git reflog
# abc1234 HEAD@{0}: reset: moving to HEAD~1
# def5678 HEAD@{1}: commit: feat: 新機能を追加
# ghi9012 HEAD@{2}: commit: fix: バグ修正

うっかり消したコミットも、reflogに残っているハッシュを使えば復元できます。

git reset --hard def5678

reflogは通常90日程度保存されるので、慌ててreset --hardをしても、当日や翌日なら復元できる可能性が高いです。「やらかしたらreflog」と覚えておきましょう。

よくあるハマりどころ

1. git reset --hard で変更が消えた

reflogを使って復元を試みてください。

git reflog
git reset --hard HEAD@{1}

2. コミットメッセージだけ直したい

直前のコミットなら --amend が使えます。

git commit --amend -m "正しいメッセージ"

pushする前なら安全、pushした後はrebaseと同じく履歴書き換えになるので注意。

3. git revert でコンフリクトした

revertも通常のマージと同様にコンフリクトすることがあります。解決方法は前章と同じです(編集 → git addgit revert --continue)。

ちゃんと使うためのポイント

  • 未コミットの変更を戻すなら restore
  • ローカルのコミットを消すなら reset
  • 共有済みのコミットを取り消すなら revert
  • reset --hard は強力だが危険、reflogで救済できると覚える
  • pushする前なら割と自由、pushした後は慎重に

次の章では、ローカルとリモートの同期について、fetch・pull・push を正しく使い分ける方法を学びます。