はじめに
Gitを使っていると「コミットをやり直したい」「変更をなかったことにしたい」という場面がよくあります。そのときによく登場するのが
git resetコマンドです。
ただし、--hard、--soft、--mixed といったオプションがあり、違いが分かりにくい…という声も多いです。
この記事では、それぞれの違いを 「対象」「影響範囲」 という観点で整理し、実際のユースケースも交えて解説します。
git resetの基本
git resetは「HEAD(現在のコミット位置)」を動かし、場合によってはインデックス(ステージングエリア)やワークツリー(作業ディレクトリ)を書き換えるコマンドです。
影響する範囲は以下の3つ:
- HEAD … どのコミットを指すか
- Index(ステージングエリア) … add 済みの変更
- Working Tree(作業ディレクトリ) … 実際のファイル
各オプションの違い
1. --softオプション
特徴
コミットだけ取り消して、変更はステージング済みの状態に残す。
- HEAD:移動する
- Index:そのまま
- Working Tree:そのまま
実際に動かしてみる
前提
・コミットA → コミットBの順でコミットが存在する
・現在HEADはコミットBを指している
・コミットAにはtest01というファイルが新規登録されている
・コミットBにはtest02、test03の2ファイルが新規登録されている
・作業ディレクトリにはtest01への修正、ステージングエリアにはtest02への修正が存在する
1.コミットログを確認
$ git log --pretty=oneline --name-only -n 2
b0f8f3a26127122d63f935515c5353a6b2be3942 (HEAD -> main) add test02 and test03
gitreset/test02
gitreset/test03
a9a4c5c61e519e55ae0e57b6b99826ba8db3347f (origin/main) add test01
gitreset/test01
test01を追加しているコミットと、test02,03を追加しているコミットが存在することを確認できます
HEADはtest02,03を追加したコミットを参照しています
2.作業ディレクトリとステージングエリアの状況を確認する
$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: gitreset/test02
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: gitreset/test01
作業ディレクトリにはtest01への修正、ステージングエリアにはtest02への修正が存在することが分かる
3.test01を登録したコミットまでリセットする
$ git reset --soft HEAD~1
4.リセット後の状態を確認する
$ git log --pretty=oneline --name-only -n 1
a9a4c5c61e519e55ae0e57b6b99826ba8db3347f (HEAD -> main, origin/main) add test01
gitreset/test01
HEADがtest01を追加したコミットを参照していることが分かる
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: gitreset/test02
new file: gitreset/test03
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: gitreset/test01
リセット後もtest01とtest02が作業ディレクトリとステージングエリアに残っていることが分かります
二つ重要なポイントがあります
一つはtest03がステージングエリアにあることです。これはリセットしたコミットにある修正がステージングエリアに戻っていることが分かります
二つ目はtest02が修正ではなく新規ファイルとなっていることです
理由はシンプルでtest02を追加したコミットがリセットされてしまったため、現在の最新コミットにはtest02が存在しません
そのためステージングに残ったtest02は新規ファイルとして認識されているというわけです
2. --mixed(デフォルト)オプション
特徴
コミットを取り消し、変更は残るがステージングは解除される。
- HEAD:移動する
- Index:リセットされる(ステージング解除)
- Working Tree:そのまま
実際に動かしてみる
前提
・コミットA → コミットBの順でコミットが存在する
・現在HEADはコミットBを指している
・コミットAにはtest01というファイルが新規登録されている
・コミットBにはtest02、test03の2ファイルが新規登録されている
・作業ディレクトリにはtest01への修正、ステージングエリアにはtest02への修正が存在する
1.コミットログを確認
$ git log --pretty=oneline --name-only -n 2
8cf22adbb793c86939148ae2c7e85bdbb57f48e9 (HEAD -> main) add test02 and test03
gitreset/test02
gitreset/test03
a9a4c5c61e519e55ae0e57b6b99826ba8db3347f (origin/main) add test01
gitreset/test01
test01を追加しているコミットと、test02,03を追加しているコミットが存在することを確認できます
HEADはtest02,03を追加したコミットを参照しています
2.作業ディレクトリとステージングエリアの状況を確認する
$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: gitreset/test02
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: gitreset/test01
作業ディレクトリにはtest01への修正、ステージングエリアにはtest02,03への修正が存在することが分かる
3.test01を登録したコミットまでリセットする
$ git reset --mixed HEAD~1
Unstaged changes after reset:
M gitreset/test01
4.リセット後の状態を確認する
$ git log --pretty=oneline --name-only -n 1
a9a4c5c61e519e55ae0e57b6b99826ba8db3347f (HEAD -> main, origin/main) add test01
gitreset/test01
HEADがtest01を追加したコミットを参照していることが分かる
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: gitreset/test01
Untracked files:
(use "git add <file>..." to include in what will be committed)
gitreset/test02
gitreset/test03
no changes added to commit (use "git add" and/or "git commit -a")
リセット後、test01とtest02,03はステージングから外れていることが分かります。
3. --hardオプション
特徴
完全に指定コミット時点の状態に戻る(変更はすべて消える)。
- HEAD:移動する
- Index:リセットされる
- Working Tree:リセットされる
実際に動かしてみる
前提
・コミットA → コミットBの順でコミットが存在する
・現在HEADはコミットBを指している
・コミットAにはtest01というファイルが新規登録されている
・コミットBにはtest02、test03の2ファイルが新規登録されている
・作業ディレクトリにはtest01への修正、ステージングエリアにはtest02への修正が存在する
3.コミットログを確認
$ git log --pretty=oneline --name-only -n 2
01ff6f0772e74a6a08ea16eb22b45a14ad892623 (HEAD -> main) add test02 and test03
gitreset/test02
gitreset/test03
a9a4c5c61e519e55ae0e57b6b99826ba8db3347f (origin/main) add test01
gitreset/test01
test01を追加しているコミットと、test02,03を追加しているコミットが存在することを確認できます
HEADはtest02,03を追加したコミットを参照しています
2.作業ディレクトリとステージングエリアの状況を確認する
$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: gitreset/test02
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: gitreset/test01
作業ディレクトリにはtest01への修正、ステージングエリアにはtest02への修正が存在することが分かる
3.test01を登録したコミットまでリセットする
$ git reset --hard HEAD~1
HEAD is now at a9a4c5c add test01
4.リセット後の状態を確認する
$ git log --pretty=oneline --name-only -n 1
a9a4c5c61e519e55ae0e57b6b99826ba8db3347f (HEAD -> main, origin/main) add test01
gitreset/test01
HEADがtest01を追加したコミットを参照していることが分かる
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
リセット後、全ての修正が作業ディレクトリとステージングから外れていることが分かります。
※ untracked fileの場合は作業ディレクトリに残る
まとめ
- --soft:コミットだけ消す(変更はステージング済みのまま)
- --mixed:コミットとステージングを消す(変更は残る)
- --hard:すべて消す(コミットも変更も残らない)
git resetはとても強力ですが、特に--hardは慎重に使いましょう。
慣れるまでは--softや--mixedを使って「取り消したいけど変更は残す」やり方から試すのがおすすめです。