株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。
Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
▶コーポレートサイト
はじめに
この記事は 「図解でわかるGitの仕組み」シリーズ の第6回です。
Gitの内部構造を、1テーマ1記事で図解していきます。
Git を使っていると「前の状態に戻したい」場面が必ず訪れます。
しかし、戻す方法は1つではありません。
-
git reset... コミット自体を取り消す -
git revert... 打ち消しコミットで変更を無効化する -
git checkout/git restore... ファイル単位で元に戻す
それぞれの違いを理解しないまま使うと、チームの履歴を壊す事故につながります。
この記事では、Git の3層構造(ワーキングツリー・ステージングエリア・リポジトリ)と対応させながら、各操作の挙動を図解で正確に説明します。
対象読者
-
git add/git commitの基本操作を理解している - 「戻す」操作を何となく使っているが、違いがよくわからない
-
reset --hardで痛い目に遭ったことがある
前提知識:Gitの3層構造
「戻す」操作を理解するには、Git の3層構造を正確に把握する必要があります。
┌─────────────────────────────────────────────────────┐
│ リポジトリ │
│ (.git/objects) │
│ コミット履歴が保存される領域 │
│ │
│ commit A ← commit B ← commit C (HEAD) │
└──────────────────────┬──────────────────────────────┘
│ git reset はここを動かす
▼
┌─────────────────────────────────────────────────────┐
│ ステージングエリア(インデックス) │
│ │
│ 次のコミットに含める変更を管理 │
└──────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ ワーキングツリー │
│ (実際のファイル群) │
│ エディタで編集している状態 │
└─────────────────────────────────────────────────────┘
git reset の3つのモードは、この3層のどこまでリセットするか が異なります。
1. git reset ― コミットを取り消す
git reset は HEAD の位置を移動させる コマンドです。
--soft、--mixed(デフォルト)、--hard の3つのモードがあり、3層構造のどこまで巻き戻すかが異なります。
準備:操作前の状態
以下の状態を出発点として、各モードの挙動を比較します。
コミット履歴: A ← B ← C (HEAD = C)
3層の状態(すべて commit C の内容で一致):
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ リポジトリ │ │ ステージング │ │ ワーキング │
│ (HEAD = C) │ │ Cの内容 │ │ Cの内容 │
└──────────────┘ └──────────────┘ └──────────────┘
--soft:コミットだけ取り消す
git reset --soft HEAD~1
【実行後】
リポジトリ: A ← B (HEAD = B) ← HEAD が B に移動
ステージング: C の内容のまま ← 変更なし
ワーキング: C の内容のまま ← 変更なし
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ リポジトリ │ │ ステージング │ │ ワーキング │
│ (HEAD = B) │ │ Cの内容 │ │ Cの内容 │
│ ← 変更 │ │ ← そのまま │ │ ← そのまま │
└──────────────┘ └──────────────┘ └──────────────┘
効果: HEAD だけが B に戻る。C での変更はステージング済みの状態で残るため、コミットメッセージを書き直してすぐに再コミットできる。
用途: コミットメッセージの修正、直前の複数コミットを1つにまとめたいとき。
--mixed(デフォルト):コミットとステージングを取り消す
git reset --mixed HEAD~1
# または
git reset HEAD~1
【実行後】
リポジトリ: A ← B (HEAD = B) ← HEAD が B に移動
ステージング: B の内容に戻る ← リセットされる
ワーキング: C の内容のまま ← 変更なし
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ リポジトリ │ │ ステージング │ │ ワーキング │
│ (HEAD = B) │ │ Bの内容 │ │ Cの内容 │
│ ← 変更 │ │ ← リセット │ │ ← そのまま │
└──────────────┘ └──────────────┘ └──────────────┘
効果: HEAD とステージングが B に戻る。C での変更はワーキングツリーに未ステージの状態で残る。git add からやり直せる。
用途: git add の対象を選び直したいとき。最も使用頻度が高い。
--hard:すべてを取り消す
git reset --hard HEAD~1
【実行後】
リポジトリ: A ← B (HEAD = B) ← HEAD が B に移動
ステージング: B の内容に戻る ← リセットされる
ワーキング: B の内容に戻る ← リセットされる
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ リポジトリ │ │ ステージング │ │ ワーキング │
│ (HEAD = B) │ │ Bの内容 │ │ Bの内容 │
│ ← 変更 │ │ ← リセット │ │ ← リセット │
└──────────────┘ └──────────────┘ └──────────────┘
効果: 3層すべてが B の状態に戻る。C での変更は完全に失われる。
用途: 変更を完全に破棄したいとき。取り消し不可能なため、最も慎重に使うべき操作。
補足:
--hardで消した変更も、コミット済みであればgit reflogから一定期間は復元可能です。ただし、未コミットの変更は復元できません。
3モード比較表
| モード | HEAD | ステージング | ワーキングツリー | 変更の行方 |
|---|---|---|---|---|
--soft |
移動 | そのまま | そのまま | ステージ済みとして残る |
--mixed |
移動 | リセット | そのまま | 未ステージとして残る |
--hard |
移動 | リセット | リセット | 完全に消える |
【図解:3モードの影響範囲】
リポジトリ ステージング ワーキングツリー
(HEAD) (インデックス) (ファイル)
--soft : ■■■■ ──── ────
--mixed : ■■■■ ■■■■ ────
--hard : ■■■■ ■■■■ ■■■■
■ = リセットされる ── = 影響なし
2. git revert ― 打ち消しコミットで安全に戻す
git revert は、指定したコミットの変更内容を 打ち消す新しいコミット を作成します。
履歴を書き換えないため、push 済みのコミットにも安全に使えます。
仕組み
git revert <コミットハッシュ>
【実行前】
A ← B ← C (HEAD)
C の内容: ファイルに「Hello」を追加した
【git revert C を実行】
A ← B ← C ← C' (HEAD)
↑
C の変更を打ち消すコミット
(「Hello」の追加を取り消す)
※ C は履歴に残ったまま、C' が追加される
【図解:revert の動作】
コミット C 打ち消しコミット C'
┌────────────┐ ┌────────────────┐
│ + Hello │ │ - Hello │
│(追加) │ → │(追加を取り消し) │
└────────────┘ └────────────────┘
履歴: A ← B ← C ← C'
↑ 新しいコミットとして追加
(C は消えない)
実行例
# コミット履歴を確認
git log --oneline
# a1b2c3d (HEAD -> main) 不要な設定ファイルを追加
# e4f5a6b ログイン機能を実装
# c7d8e9f 初期コミット
# 直前のコミットを打ち消す
git revert a1b2c3d
実行するとエディタが開き、打ち消しコミットのメッセージを編集できます。デフォルトでは Revert "不要な設定ファイルを追加" のようなメッセージが生成されます。
3層構造の変化
【revert 実行後】
リポジトリ: A ← B ← C ← C' (HEAD = C')
ステージング: C' の内容(= C の変更を打ち消した状態)
ワーキング: C' の内容
※ 3層すべてが C' の内容で一致する
revert は新しいコミットを作成するため、3層構造はすべて新しいコミットの内容で一致した状態になります。
複数コミットの revert
# 連続する複数コミットを一括で revert(新しい方から順に指定)
git revert HEAD~2..HEAD
# エディタを開かずにデフォルトメッセージで revert
git revert --no-edit <コミットハッシュ>
注意: 複数コミットを revert する場合は、新しいコミットから順に 打ち消す必要があります。古いコミットから revert するとコンフリクトが発生しやすくなります。
3. git checkout / git restore ― ファイル単位で戻す
コミット全体ではなく、特定のファイルだけ を元の状態に戻したい場合があります。
git restore(Git 2.23 以降で推奨)
git restore は Git 2.23 で git checkout のファイル復元機能を分離して導入されたコマンドです。
ワーキングツリーの変更を取り消す(add 前)
git restore <ファイル名>
【実行前】
ステージング: 最後のコミットの内容
ワーキング: 編集済み(まだ add していない)
【git restore <ファイル名> を実行】
ステージング: 最後のコミットの内容 ← 変更なし
ワーキング: 最後のコミットの内容 ← ステージングの内容に戻る
┌──────────────┐ ┌──────────────┐
│ ステージング │ │ ワーキング │
│ 変更なし │ →│ 元に戻る │
└──────────────┘ └──────────────┘
注意: この操作で失われた変更は復元できません。コミットもステージングもされていない変更は Git の管理外です。
ステージングの取り消し(add の取り消し)
git restore --staged <ファイル名>
【実行前】
ステージング: 編集済み(add 済み)
ワーキング: 編集済み
【git restore --staged <ファイル名> を実行】
ステージング: 最後のコミットの内容 ← HEAD の内容に戻る
ワーキング: 編集済み ← 変更なし
┌──────────────┐ ┌──────────────┐
│ ステージング │ │ ワーキング │
│ 元に戻る │ │ 変更なし │
└──────────────┘ └──────────────┘
ファイルの変更自体はワーキングツリーに残るため、安全な操作です。
特定のコミット時点のファイルに戻す
git restore --source=<コミットハッシュ> <ファイル名>
【実行後】
ステージング: 変更なし
ワーキング: 指定コミット時点の内容に置き換わる
git checkout によるファイル復元(従来の方法)
Git 2.23 より前のバージョンでは、git checkout がブランチの切り替えとファイルの復元の両方を担っていました。
# ファイルを最後のコミット状態に戻す(git restore と同等)
git checkout -- <ファイル名>
# 特定コミット時点のファイルに戻す(git restore --source と同等)
git checkout <コミットハッシュ> -- <ファイル名>
推奨: Git 2.23 以降では
git restoreの使用が推奨されています。git checkoutは「ブランチ切り替え」と「ファイル復元」の2つの機能を持つため、意図が不明確になりがちです。Git 2.23 以降では、ブランチ切り替えにはgit switch、ファイル復元にはgit restoreを使い分けることが推奨されています。
checkout / restore の3層構造への影響
【比較表】
操作 | ステージング | ワーキングツリー
─────────────────────────────────|───────────|──────────────
git restore <file> | 変更なし | 元に戻る
git restore --staged <file> | 元に戻る | 変更なし
git restore --staged --worktree | 元に戻る | 元に戻る
git checkout -- <file> | 変更なし | 元に戻る
4. reset と revert の使い分け
判断基準:push 済みかどうか
「戻したいコミットは push 済み?」
│
┌────┴────┐
│ │
YES NO
│ │
▼ ▼
revert reset
を使う を使う
理由: 理由:
履歴を ローカルなら
書き換えない 履歴の書き換え
ので安全 が許される
詳細比較
| 観点 | git reset |
git revert |
|---|---|---|
| 履歴の書き換え | する(コミットが消える) | しない(新しいコミットを追加) |
| push 済みコミットへの使用 | 危険(force push が必要) | 安全 |
| 影響範囲 | 指定コミット以降すべて | 指定したコミットのみ |
| 元に戻す操作 |
git reflog + git reset
|
打ち消しコミットを revert |
| チーム開発での推奨度 | ローカルのみ | 共有ブランチで推奨 |
なぜ push 済みに reset を使ってはいけないのか
【push 済みコミットを reset した場合】
あなたのローカル: A ← B (reset で C を消した)
リモート: A ← B ← C (C がまだある)
同僚のローカル: A ← B ← C (pull 済み)
→ あなたが push しようとすると拒否される
→ force push すると同僚の履歴と矛盾が発生
→ 同僚が pull すると、C が再び現れたり、
コンフリクトが発生したりする
【push 済みコミットを revert した場合】
あなたのローカル: A ← B ← C ← C' (revert で C' を追加)
リモート: A ← B ← C (push 前)
同僚のローカル: A ← B ← C (pull 済み)
→ 通常の push が成功する
→ 同僚が pull すると C' が追加されるだけ
→ 履歴の矛盾は一切起きない
5. 操作早見表
┌─────────────────────────────────────────────────────────┐
│ 「戻す」操作の選び方 │
├─────────────────────────────────────────────────────────┤
│ │
│ 何を戻したい? │
│ │ │
│ ├─ ファイルの変更(add 前) │
│ │ → git restore <file> │
│ │ │
│ ├─ add の取り消し │
│ │ → git restore --staged <file> │
│ │ │
│ ├─ 直前のコミット(ローカルのみ) │
│ │ ├─ 変更を残したい → git reset --soft HEAD~1 │
│ │ ├─ add からやり直し → git reset HEAD~1 │
│ │ └─ 完全に破棄 → git reset --hard HEAD~1 │
│ │ │
│ └─ push 済みのコミット │
│ → git revert <hash> │
│ │
└─────────────────────────────────────────────────────────┘
6. 実践シナリオ
シナリオ1:コミットメッセージを間違えた(ローカルのみ)
# 方法1: --amend を使う(最も簡単)
git commit --amend -m "正しいコミットメッセージ"
# 方法2: reset --soft で戻してから再コミット
git reset --soft HEAD~1
git commit -m "正しいコミットメッセージ"
シナリオ2:間違ったファイルを add してしまった
# ステージングから外す(ファイルの変更は残る)
git restore --staged secret.env
シナリオ3:直前の3コミットを1つにまとめたい
# 3つ前のコミットまで soft reset
git reset --soft HEAD~3
# まとめて1つのコミットにする
git commit -m "ユーザー認証機能を実装"
シナリオ4:push 済みのコミットに誤りがあった
# 打ち消しコミットを作成(安全)
git revert <問題のコミットハッシュ>
# 修正版をコミット
git add .
git commit -m "ユーザー認証のバリデーションを修正"
git push
シナリオ5:reset --hard を間違えて実行してしまった
# reflog で操作履歴を確認
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5a6b HEAD@{1}: commit: 重要な機能を追加 ← この状態に戻りたい
# reflog のハッシュを指定して復元
git reset --hard e4f5a6b
注意:
git reflogで復元できるのはコミット済みの変更のみです。一度もコミットしていない変更は復元できません。
まとめ
| 操作 | 目的 | 履歴の書き換え | 安全性 |
|---|---|---|---|
reset --soft |
コミットだけ取り消す | あり | ローカルなら安全 |
reset --mixed |
コミット + add を取り消す | あり | ローカルなら安全 |
reset --hard |
すべてを取り消す | あり | 変更が消える |
revert |
打ち消しコミットで戻す | なし | 安全 |
restore |
ファイル単位で戻す | なし | 未コミット変更は消える |
checkout -- <file> |
ファイル単位で戻す(従来) | なし | 未コミット変更は消える |
判断の原則:
-
ローカルのみ のコミット →
reset -
push 済み のコミット →
revert -
ファイル単位 で戻す →
restore(Git 2.23 以降)
次回予告
第7回では 「stash/cherry-pick/rebase -i」 を図解で解説します。作業を自在に操る便利コマンドを、実務での使いどころとともに紹介します。
シリーズ目次
| # | テーマ |
|---|---|
| 1 | Gitの全体像 ― ワークツリー・ステージ・リポジトリの3層構造 |
| 2 | ブランチとHEAD ― 「枝分かれ」の正体を理解する |
| 3 | マージとリベース ― 統合戦略の違いを図で比較 |
| 4 | リモートとローカル ― push/pull/fetchの裏側 |
| 5 | コンフリクト解消 ― なぜ起きるのか、どう直すのか |
| 6 | reset/revert/checkout ― 「戻す」操作を正しく使い分ける(この記事) |
| 7 | stash/cherry-pick/rebase -i ― 実務で頼れる便利コマンド |
| 8 | .gitignore・フック・設定 ― Gitをカスタマイズする |
| 9 | Git Flow vs GitHub Flow ― ブランチ戦略の選び方 |
| 10 | トラブルシューティング ― 「やらかした!」を救うコマンド集 |
参考
- Git 公式ドキュメント - git-reset
- Git 公式ドキュメント - git-revert
- Git 公式ドキュメント - git-restore
- Git 公式ドキュメント - git-checkout
- Pro Git Book - Reset Demystified
- Pro Git Book - Undoing Things
「この記事が役に立った」と思ったら、LGTM とストックをお願いします。
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!