git reset --soft でステージング状態に戻る謎

git reset --soft は、HEADを移動させるが index は変化させないとされています。

しかしながら、実際には未ステージングからステージング状態に***変化***します。

これはどういうことだろう、git add で登録するindexが変化しないのであれば、ステージング状態も変化しないはずでは？
との疑問から調べた結果、以下のことが分かったのでまとめてみました。

- ステージング状態は、indexの内容では無く、commitとindexの**差分**を表示したものである

# commitとindexとworking tree
まずは、これら3者の関係を整理しておきます。

## コミット直後
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/9023c1e9-a6ca-843c-fb69-5d6b4b88a5b9.png)

HEADは、カレントのコミット位置です。
A, B, Cは、commitです。
indexは、git add により登録される領域です。
working treeが、実際に編集作業を行っているファイル領域です。

コミット直後では、これら3者の内容が一致しています。

## working treeを編集
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/e8ae4ee9-8515-687b-2078-7f373c0c4394.png)


この時、git diff コマンドの出力には、indexとworking treeの差分が表示されます。
git statusコマンドでは、Modified や Untracked ファイルとして表示されています。

## indexに登録
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/463fad90-551f-9bd8-e777-aed24a90a9ee.png)

git add で、working treeの内容がindexに登録されます。

その結果、git diff では、indexとworking treeが一致しているので差が出ません。
git diff --cached では、commitとindexを比較するので、差が出ます。

この、commitとindexの**"差"**がステージング状態として見えているのです。

## commit する
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/c139acbb-5914-9958-705d-5324420b792d.png)

commitとindexとworking treeが一致するので、差が出なくなります。
commitとindexに差が無いのでステージング状態もクリアされます。

indexは、commit後も維持されます。


# resetコマンドのオプションによる違い
## git reset
オプションの --mixed 指定と同じです。(デフォルト動作)
commitとindexを移動させます。(commitの移動 = HEADの移動)

移動前：
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/1c9f14e4-2329-7d21-6b35-97a7767371b0.png)

2つ前のコミットに移動させてみます。
``` git reset HEAD~2 ```

移動後：
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/44982ff6-cc98-5d8a-0f11-1b20b8d13ab8.png)

HEADの移動先のcommit内容がindexに反映されます。
ローカルのworking treeのファイルはそのままです。

結果、ステージング状態のファイルは無く、git diff で差分が出ます。

## git reset --hard
commitとindexとworking treeを移動させます。

移動前：
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/1c9f14e4-2329-7d21-6b35-97a7767371b0.png)

``` git reset --hard HEAD~2 ```

移動後：
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/7eddf213-8185-d685-1a4e-e7ac6f3eaaf1.png)


HEADの移動先のcommit内容がindexとworking treeに反映されます。

結果、ステージング状態のファイルが無く、git diff でも差が出ません。

## git reset --soft
commitだけ動かします。

移動前：
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/1c9f14e4-2329-7d21-6b35-97a7767371b0.png)


``` git reset --soft HEAD~2 ```

移動後：
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/e75c7c0d-6a18-ac16-19aa-8b0dcbe7ae46.png)

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に反映するという動作をするので、

取得前：
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/1c9f14e4-2329-7d21-6b35-97a7767371b0.png)

``` git checkout HEAD^ -- hello.c ```

取得後：
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/f4c31da4-966d-1a9f-7524-fce304ef0a88.png)

コミットBのhello.cの情報が、現在(HEAD位置)のindexとworking treeに、反映(マージ)されます。
結果、indexと現在のHEAD位置のコミットとの間に差が生じるのでステージング状態になる、という訳です。

# 参考資料
[Git - git-reset Documentation](https://git-scm.com/docs/git-reset)
[[Git] 脱初級！誤コミットはもう怖くない](http://qiita.com/forest1/items/fcd2a2961a3faac27041)