git reset --soft は、HEADを移動させるが index は変化させないとされています。
しかしながら、実際には未ステージングからステージング状態に変化します。
これはどういうことだろう、git add で登録するindexが変化しないのであれば、ステージング状態も変化しないはずでは?
との疑問から調べた結果、以下のことが分かったのでまとめてみました。
- ステージング状態は、indexの内容では無く、commitとindexの差分を表示したものである
commitとindexとworking tree
まずは、これら3者の関係を整理しておきます。
コミット直後
HEADは、カレントのコミット位置です。
A, B, Cは、commitです。
indexは、git add により登録される領域です。
working treeが、実際に編集作業を行っているファイル領域です。
コミット直後では、これら3者の内容が一致しています。
working treeを編集
この時、git diff コマンドの出力には、indexとworking treeの差分が表示されます。
git statusコマンドでは、Modified や Untracked ファイルとして表示されています。
indexに登録
git add で、working treeの内容がindexに登録されます。
その結果、git diff では、indexとworking treeが一致しているので差が出ません。
git diff --cached では、commitとindexを比較するので、差が出ます。
この、commitとindexの**"差"**がステージング状態として見えているのです。
commit する
commitとindexとworking treeが一致するので、差が出なくなります。
commitとindexに差が無いのでステージング状態もクリアされます。
indexは、commit後も維持されます。
resetコマンドのオプションによる違い
git reset
オプションの --mixed 指定と同じです。(デフォルト動作)
commitとindexを移動させます。(commitの移動 = HEADの移動)
2つ前のコミットに移動させてみます。
git reset HEAD~2
HEADの移動先のcommit内容がindexに反映されます。
ローカルのworking treeのファイルはそのままです。
結果、ステージング状態のファイルは無く、git diff で差分が出ます。
git reset --hard
commitとindexとworking treeを移動させます。
git reset --hard HEAD~2
HEADの移動先のcommit内容がindexとworking treeに反映されます。
結果、ステージング状態のファイルが無く、git diff でも差が出ません。
git reset --soft
commitだけ動かします。
git reset --soft HEAD~2
indexとworking treeは変化しないので git diffで差が出ませんが、
commitとindexには差が有るので、ステージング状態となります。
あくまでもcommitとindexの**"差"**ですので、このケースではステージングの内容もコミット2つ分となります。
つまり、最後にgit addした内容に関わらず、git reset --soft による移動先次第でステージング状態が変化するということです。
なので、この状態からさらに、git reset --soft HEAD^ とすると、ステージング内容がコミット3つ分に変化します。
おまけ
checkoutで別コミットからファイルを取得するとステージング状態になる理由
例えば、変更しないはずのファイルを間違ってコミットしてしまった場合に、
git checkout HEAD^ -- hello.c
これで、変更前のファイルを取ってくることが出来ます。
この時、取得したファイルが何故かステージング状態となります。
その理由は。
git checkoutコマンドは、git reset --hard 同様、
commitの内容をindexとworking treeに反映するという動作をするので、
git checkout HEAD^ -- hello.c
コミットBのhello.cの情報が、現在(HEAD位置)のindexとworking treeに、反映(マージ)されます。
結果、indexと現在のHEAD位置のコミットとの間に差が生じるのでステージング状態になる、という訳です。