git reset --soft, --mixed, --hard の違い
Git の reset コマンドに付け加えられる, ちょっとややこしい 3 つのオプション "--soft", "--mixed", "--hard" の違いを紹介します.
Git の reset コマンドはコミットレベルで使われる場合とファイルレベルで使われる場合がありますが, 今回はコミットレベルで使われることを想定された --soft
, --mixed
, --hard
の 3 つのオプションの違いを紹介したいと思います.
Git の reset コマンドをコミットレベルで使うということは, 特定の古いコミットまでコミットヒストリを遡り, 遡るまでのコミットを破棄してしまうということです.
例えば次のような A
, B
, C
, D
という一連のコミットがあったとして:
HEAD
|
v
master
|
v
A --- B --- C --- D
次の reset コマンドを打ち:
git reset HEAD~2
次のように C
と D
を破棄して B
まで遡りたいといった時に使われます:
HEAD
|
v
master
|
v
A --- B
ただそのように reset コマンドは今まで存在していたいくつかのコミットを破棄して, コミットヒストリを書き換えてしまうので, 他の contributor と共有のパブリックレポジトリで使うことは混乱を招くので控えるべきとされています.
なので reset コマンドを使うときは, 他の contributor と共有していない contributor が自分一人だけのプライベートレポジトリやブランチでのみ使うようにしましょう.
なので reset コマンドを使うときは, 注意して使われる必要があるかと思います.
ということで reset コマンドの基本的な説明をさせていただきましたが, 本題の --soft
, --mixed
, --hard
という 3 つのオプションのそれぞれの違いを紹介したいと思います.
それぞれのオプションに共通することは HEAD が指し示すブランチの先端を移動させるということです.
例えば次の 3 つのコマンドのどれを実行しても:
# 1
git reset --soft HEAD~2
# 2
git reset --mixed HEAD~2
# 3
git reset --hard HEAD~2
このようなものが:
HEAD
|
v
master
|
v
A --- B --- C --- D
このようになります:
HEAD
|
v
master
|
v
A --- B
冒頭で示した 2 つの図と全く同じですね.
では一体それら 3 つのコマンドはどういう違いがあるのでしょうか.
答えは Working Directory と Index という 2 つの領域内での違いになります.
ちなみに Working Directory, Index, HEAD これら 3 つは Git の 3 つのツリーと言われています.
Working Directory というのは, ファイルの現在の状態が保存されている領域で, Index というのは git add
コマンドで Index に移されたファイルの状態が保存されている領域です.
Index は Staging Area とも言われます.
例えば次のようなコミットをしてきたとします:
HEAD
|
v
master
|
v
A --- B --- C --- D
ご覧のように現在の HEAD は master ブランチで D
を指しています.
それぞれのコミットの内容は次のようになります:
A
- ファイル
README.md
を作成した
- ファイル
B
- ファイル
README.md
に変更を加えた - ファイル
example.txt
を作成した
- ファイル
C
- ファイル
README.md
にさらに変更を加えた
- ファイル
D
- ファイル
example.txt
に変更を加えた
- ファイル
こういったコミットをしてきたという前提でそれぞれ 3 つのオプションの違いを見ていきたいと思います.
git reset --soft HEAD~2
--soft
が使われると reset コマンドで古いコミットに遡ったとしても, 遡る前の Working Directory と Index に保持されていたファイルの状態を引き継ぎます.つまりは
HEAD~2
というのは HEAD から 2 つ前のコミットに遡りB
というコミットに移動するということですが,B
に移動してC
とD
のコミットを破棄したとしてもC
も含まれたD
の Working Directory と Index の状態を保ったままB
に移動するということで,B
の Working Directory と Index の状態にならないということです.このコマンドを実行して HEAD を
B
に移動させてからgit status
で現在の Wokring Directory と Index の状態を確認すると次のようになります:On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: README.md modified: example.txt
このように
--soft
を使うとB
にリセットしてもC
とD
でコミットした内容が Working Directory と Index の両方で保持されているのがわかります.Working Directory に加えて Index の状態も保持するということでソフトということなのでしょう.
git reset [--mixed] HEAD~2
--mixed
を使うと Working Directory はD
のままで変更されず, Index のみHEAD~2
が表すB
のコミットの Index に変更させます.--mixed
はデフォルトで使われるオプションなので, 次のように省略することもできます:git reset HEAD~2
このコマンド実行後,
git status
の結果は次のようになります:On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: README.md modified: example.txt
このように
D
の Working Directory が保持されています. Index はB
の Index に変更されたのでChanges to be committed:
という行が表示されていません. これは Index と HEAD のファイルの状態がB
と完全に一致しているためです.Working Directory だけ
D
のものなので,Changes not staged for commit:
という行のあとにB
とD
の Working Directory のファイルの差分の変更が表示されています.つまり
B
の Working Directory のファイルの状態と異なるのでmodified
されたものとして, その差分が表示されています.--soft
は Working Directory と Index の両方の状態を保持するのでソフトと名付けられました.次に紹介する
--hard
というオプションは Working Directory と Index の両方を状態を保持せず, リセット先のコミットの Working Directory と Idnex の状態に変更します.このように
--hard
でリセットすると--soft
と異なり何も引き継がないでコミットヒストリを遡るので復元がきかず厳格ということでハードと名付けられているのでしょう.なので
--mixed
というのは--soft
と--hard
を足して 2 で割ったように Working Directory の内容は保持するけれども Index の内容は保持しないということでミックスと名付けられているのでしょうね.git --hard HEAD~2
--mixed
のときに説明したことと繰り返しになりまずが--hard
はリセット前の Working Directory と Index の内容を引き継がないで指定された古いコミットに遡ります.このコマンドの場合は
D
の Working Directory と Index の状態をB
に引き継がないということです.なので一度このコマンドを実行してから
git status
を打つと:On branch master nothing to commit, working tree clean
このように表示され
D
のファイル情報が Working Directory と Index のどちらにもないのでD
の状態を復元する余地がありません.完全に Working Directory, Index 共に HEAD と同じ
B
の状態になっています.--soft
や--mixed
と違い, もうD
の時の情報が残されていないので不可逆的な操作ということです.ただ完全に不可逆的かというと実はそういうこともありません.
git reflog
というコマンドを使ってリセットする前のコミット ID を確認してそのコミット ID をgit reset --hard <commit id>
に渡して実行するとリセットする前の状態に戻すことができます.ただ基本的にはやり直しがきかない不可逆的な操作として
--hard
オプシヨンを reset コマンドで使うときは慎重に使われることをお勧めします.なんたってハードですからね.
と --soft
, --mixed
, --hard
のそれぞれの違いを説明させていただきました.
それぞれ Working Directory と Index のファイルの状態が変更されるかされないかということを, 変更されるは Yes, 変更されないは No として表にまとめると次のようになります:
Option | Working Directory | Index |
---|---|---|
--soft | No | No |
--mixed | No | Yes |
--hard | Yes | Yes |
これら 3 つのオプションはこのように具体的に挙動が異なりますので, それぞれ状況に応じて使い分けていただければと思います.
最後までお読みくださいましてありがとうございました.
関連記事
Git の Reset, Checkout, Revert の違い2018.06.01
Git のコミット履歴を大胆に書き換えるなら git rebase -i がオススメ2018.08.23
git checkout コマンドで使われる -- の意味2018.05.28
Git の mergetool で vimdiff を指定して Vim でマージコンフリクトを解決してみた2018.08.20
Git のマージコンフリクトを解決する方法2018.06.13