0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【図解でわかるGit 第6回】reset/revert/checkout ― 「戻す」操作を正しく使い分ける

0
Posted at

株式会社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 トラブルシューティング ― 「やらかした!」を救うコマンド集

参考


「この記事が役に立った」と思ったら、LGTM とストックをお願いします。

@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?