大きな画面での利用をおすすめします — アニメーションと操作がより快適になります。
入力

変更を取り消す

Git の取り消しには、スナップショットのコピー、参照の移動、逆変更を記録する新コミットがある。git restore、git reset、git revert は同じファイル内容を作れても、インデックス、履歴、オブジェクト数、復元用ポインターが異なる。

作業ツリー、インデックス、HEAD -> main のコミットは別の場所だ。restore は版をコピーし、reset はブランチを動かして必要なファイル層を戻し、revert は訂正記録を加える。同じ内容でも同じ履歴ではない。

三つの取り消しの仕組み

git restore app.txt は既定でインデックスから作業ツリーを上書きし、main、インデックス、オブジェクトを変えない。--staged を付けると既定の元は HEAD のツリー、先はインデックスになる。ファイル層の操作で、履歴は変えない。

git reset <target> は先に対象を解決し、旧先端を ORIG_HEAD に記録して main を動かす。モードが残りのコピーを決める。

モードmain を移動対象をインデックスへ対象を作業ツリーへ新コミット
--softはいいいえいいえなし
--mixedはいはいいいえなし
--hardはいはいはいなし

soft は元のインデックスと作業ファイルを保ってブランチを動かせる。mixed はインデックスも替え、hard は追跡済み作業ファイルも上書きする。ORIG_HEAD が持つのはコミット ID だけで、未ステージ・未コミット編集のバックアップではない。

git revert HEAD は選んだコミットと親から逆変更を計算し、インデックスと作業ツリーへ適用して、新コミットで main を進める。取り消したコミットは祖先に残る。後退せず、新しいスナップショットで訂正を記録する。

変更を取り消すステップ

既定は未誕生の main から始める。

  1. edit app.txt が作業ツリーだけに mode=safe を書く。
  2. git add app.txt が blob を保存し、インデックスをそこへ向ける。
  3. git commit -m "base" がインデックスのツリーと根 C1 を作り、main を進める。三層は全て mode=safe。
  4. 編集で作業ツリーだけを mode=draft にする。
  5. git restore app.txt は既定のインデックスから mode=safe をコピーする。draft は消え、main とインデックスは変わらない。
  6. 作業ツリーを mode=unsafe に編集する。
  7. add が unsafe blob を保存し、インデックスを更新する。
  8. git commit -m "bad-change" が C1 を親に C2 を作り、main を進める。三層は unsafe になる。
  9. git reset --soft HEAD^ が C1 を解決し、C2 を ORIG_HEAD に書いて main を C1 へ戻す。インデックスと作業ツリーは unsafe のままなので、新 HEAD に対して変更がステージされた状態になる。
  10. git reset --soft ORIG_HEAD は保存済み C2 を先に解決し、その後 ORIG_HEAD を C1 に替え、main を C2 へ進める。両ファイル層は変えない。先に復元先を読まないと、上書き後の誤ったコミットを対象にしてしまう。
  11. git revert HEAD は両層が C2 と一致することを確認し、C2 と C1 から mode=unsafe -> mode=safe を求めて適用する。C1 のツリーを再利用でき、blob やツリーを増やさず、コミット C3 だけを作る。main は C3 へ進み、C2 はその親として到達可能なまま。

最後のファイルは C1 と同じ safe。C1 への hard reset でも同じ内容にできるが、ブランチは後退し、コミットは二つだけになる。revert は三つ目へ前進し、悪いコミットを親に残す。内容だけでは経緯を判断できない。

各コマンドの書き込み先

コマンド作業ツリーインデックスmainオブジェクト庫
restore app.txtインデックスからコピー不変不変不変
restore --staged app.txt不変HEAD からコピー不変不変
reset --soft不変不変移動不変
reset --mixed不変対象からコピー移動不変
reset --hard対象からコピー対象からコピー移動不変
revert HEAD逆変更の結果逆変更の結果前進この例では一コミット追加

一ファイルのエンジンでは B バイトのコピーやハッシュは O(B)。単一親の隣接対象 HEAD^ の解決は O(1)、main からの到達可能性の分類は O(V + E)。ブラウザモデルの計算量であり、Git のインデックス、pack、履歴走査、ファイルシステムの性能保証ではない。

境界ケース

  • 未誕生 HEAD はツリーがなく、restore --staged の既定の元がない。
  • 根には第一親がないので HEAD^ は失敗し、空ツリーには解決しない。
  • 本モデルの ORIG_HEAD は最初の成功 reset まで未設定。未知または曖昧な短 ID も変更なしで失敗する。
  • コピー先と元が同じ restore は何も変えず、参照やオブジェクトも書かない。
  • インデックスか作業ツリーが HEAD と違えば、本モデルは revert を拒否し、未実装の競合手順を作らない。
  • hard reset は Git がオブジェクトとして保存したことのない作業バイトを捨てうる。コミットの復元ポインターでその編集は戻せない。

よくある間違い

  • restore がファイルを移動すると言う。版を先へコピーし、元は残る。
  • reset がコミットを削除すると言う。参照を動かすだけで、旧コミットは ORIG_HEAD などに残りうる。掃除は別処理だ。
  • ORIG_HEAD を reflog と同じに扱う。前者は一 ID、後者は独立した参照更新の履歴。
  • revert がパッチオブジェクトを保存すると言う。逆変更を求め、その結果のツリーと親を新コミットとして記録する。
  • コミットを復元できるから hard reset の全てを戻せると思う。上書きした未保存の作業編集は戻せない場合がある。

モデルの範囲

一つのローカルリポジトリ、接続した HEAD -> main、通常の追跡済み一ファイル、単一親履歴で、全ファイル restore、三種 reset、クリーンな revert HEAD を扱う。固定識別情報と実 Git 形式の SHA-1 ID を使う。

パス集合、パッチモード、削除、改名、モード、未追跡ファイルの妨害、分離 HEAD、複数ブランチ、マージ、revert -m、旧コミットの競合、revert シーケンサー、フック、署名、リモート、reflog 期限、ガベージコレクション、エディターやファイルシステムの復元は省く。実際の選択肢と上書き動作は git restore、git reset、git revert を参照できる。