[git reset (--hard/--soft)]ワーキングツリー、インデックス、HEADを使いこなす方法


# 1. gitの基礎（言葉の意味）
- ワーキングツリー[working tree]：最新のファイルの状態
- インデックス[index]（ステージ[stage]）：コミットするためのファイルの状態
- ローカルリポジトリ[local repository]：ファイルの変更履歴を記録（手元で管理）
  - ヘッド[HEAD]：最新のコミットの状態
- リモートリポジトリ[remote repository]：ファイルの変更履歴を記録（みんなで共有）

![2015_0307commit図_2.jpg](https://qiita-image-store.s3.amazonaws.com/0/41481/60057667-95aa-b9b1-141f-f2d2f0c50d1e.jpeg)

- add：「ワーキングツリー → インデックス」への反映
- commit：「インデックス → ローカルリポジトリ」への反映
- push：「ローカルリポジトリ → リモートリポジトリ」への反映

# 2. git resetを使いこなす
`git reset`には`--hard`、`--mixed`、`--soft`の3つのオプションがある。

<p style="font-size: 11px">※厳密にはもっとあります。詳しくは`git reset -h`を叩くなどして見てください。</p>

それぞれのオプションにおいて、修正の及ぶ範囲は下記。

- `--hard`：「HEADの位置・インデックス・ワーキングツリー」全て
- `--mixed`（or オプション無し）：「HEADの位置・インデックス」
- `--soft`：「HEADの位置」のみ

現在のファイルの状態をYとする。
![2015_0307git_reset_1_.jpg](https://qiita-image-store.s3.amazonaws.com/0/41481/33410c07-ef2c-e633-9a3f-f613107ed7b2.jpeg)

- ファイルを変更したらワーキングツリーが変更
- addしたらインデックスが変更
- commitしたらHEAD（ = 最新のコミットのハッシュ値のエイリアス）が1つ前に進む

![2015_0307git_reset_2_.jpg](https://qiita-image-store.s3.amazonaws.com/0/41481/6c07d146-bd58-69d1-c858-2e18eafe4c72.jpeg)
変更をコミットしてファイルの状態がZに移ったとする。
![2015_0307git_reset_3_.jpg](https://qiita-image-store.s3.amazonaws.com/0/41481/13edb774-7834-f20e-3d81-c45172951377.jpeg)

- `reset --hard`：全部Yに戻す。
- `reset --mixed`：`commit`と`add`の取り消し。
- `reset --soft`：`commit`のみ取り消し。

![2015_0307git_reset_4_.jpg](https://qiita-image-store.s3.amazonaws.com/0/41481/a38e1a34-855d-5497-6ea2-d4c7e4eb41d0.jpeg)



### ★主な利用シーン★
#### ① 直前のコミットを取り消したい（コミットのみ取り消し）
```
git reset --soft HEAD^
```
- `HEAD^`：直前のコミットを意味する。
  - `@^`、`HEAD~`も同じ意味。
  - `HEAD~{n}` ：n個前のコミットを意味する。
  - `HEAD^`や`HEAD~{n}`の代わりにコミットのハッシュ値を書いても良い。
  - gitのv1.8.5からは、「`HEAD`」のエイリアスとして「`＠`」が用意されている。
  - `HEAD^^^`と`HEAD~3`と`HEAD~~~`と`HEAD~{3}`と`@^^^`は同じ意味。
- `--soft`なので、インデックス・ワーキングツリーはそのまま。
- 上書きコミットしたいなら、`git commit --amend`を使うとラク！

#### ② 直前のコミットを取り消したい（マルっと消したい）
```
git reset --hard HEAD^
```
- `--hard`なので、「インデックス・ワーキングツリー」も1つ前のコミットの状態に戻る。

#### ③ コミット後の変更を全部消したい
```
git reset --hard HEAD
```
- `--hard`なので、「HEADの位置・インデックス・ワーキングツリー」を全て`HEAD`の位置に動かす。
  - `HEAD`の位置はそのまま。
  - 「インデックス・ワーキングツリー」の変更が消せる。

#### ④ addを取り消したい
```
git reset --mixed HEAD
```

または、

```
git reset HEAD
```
- `--mixed`（or オプション無し）なので、「HEADの位置・インデックス」を`HEAD`の位置に動かす。
  - `HEAD`の位置はそのまま。
  - 従って、インデックスの変更（addした内容）のみ元に戻す。

#### ⑤ すごい昔の状態で動作を確認したい
```
git reset --hard 昔のコミットのハッシュ値
```
- これをやる前に、`push`して置くことを強くオススメします！
- `reset --hard`は基本的にすべて消えるので慎重に。
  - 一応復活させる術はなくはない。
- `remote`に最新の状態を記録しておけば、ローカルは好きな所に移動しても安心。
- コミットのハッシュ値は`git log`で確認すればOK！

動作確認後、最新の状態に戻るには、また、

```
git reset --hard ORIG_HEAD
```

をすれば良い。

- `git reset --hard ORIG_HEAD`：直前のresetをなかったことにするおまじない！
- `git reset`は未来の状態にも行ける。
- `git reset --hard 最新のコミットのハッシュ値`でも良い。
- `git rebase origin/master`でも良い。
  - rebaseで何か問題が起きたら下記参照。
  - http://qiita.com/shuntaro_tamura/items/c505b76c1021a35ca9ff
- `reset --hard`や`rebase`する時は、こまめにソースツリーの状態を確認しておくといいと思います。

# 3. 間違えてgit resetしちゃったら・・・
直前のリセットを取り消したいなら、

```
git reset --hard ORIG_HEAD
```

をすればOK！

`reset`後色々いじってしまっていたら、慌てずに、`git reflog`コマンドを叩きましょう。

- `git log`：コミットの履歴を見る
- `git reflog`：これまでHEADが辿ってきた履歴を見る



`git reflog`で戻したい状態を見つけ出し、

```
git reset [（必要なら）オプション] [ハッシュ値]
```

をすれば良い。
`reset`で困ったときは「`reset`＆`reflog`」と覚えておこう！

### (注) reflogは万能ではない
`git reflog`は、「これまでHEADが辿ってきた履歴」を保持しているものです。
`add`しただけで`commit`してないものがある時に、`reset --hard`をしてしまったら、`add`した内容は復元不可能です。
`add`すらしてないものももちろん消えます。

- `git reset`を使うときはくれぐれも慎重に！

# 4. おまけ（gitを使いやすくする）
#### ① ローカルの変更内容を一時的に退避させる。
```
git stash
```

退避させた内容を元に戻すには、下記を叩けばOK！

```
git stash pop
```

- その他`stash`系コマンドは下記参照。
- http://kentana20.hatenablog.com/entry/2014/07/05/172717

#### ② aliasを作成
gitのaliasは`~/.gitconfig`に書き込むことが可能。

```
vim ~/.gitconfig
```

```~/.gitconfig
[alias]
	ad = add
	br = branch
	ci = commit
	co = checkout
	ft = fetch
	pl = pull
	ps = push
	sh = show
	st = status
```

ただ、個人的には`git`を打つのも面倒なので`~/.bashrc`にgit系のaliasも書いている。

```
vim ~/.bashrc
```

```~/.bashrc
# git
alias g='git'
alias gb='git branch'
alias gs='git status'
alias gd='git diff'
alias gds='git diff --staged'
alias ga.='git add .'
alias gcm='git commit -m'
alias gcam='git commit -am'
alias gpod='git push origin develop'
alias gp='git push'
alias gfo='git fetch origin'
alias grod='git rebase origin/develop'
alias gsp='git stash pop'
alias gsu='git submodule update --init --recursive'
alias gcd='git checkout develop'
alias gmnf='git merge --no-ff'
```

masterを直接いじることをせずdevelopブランチ切ることが多いため、上のようなaliasにしている。
あと、運用方法の好みとして、`pull`せず`rebase`する風潮が自分のまわりであるので、`gp`で`push`の意味も持たせてしまっている。

#### ③ その他
▼git rebase 失敗した時の対処法
http://qiita.com/shuntaro_tamura/items/c505b76c1021a35ca9ff

▼Gitで補完機能を有効にする方法
http://qiita.com/shuntaro_tamura/items/9ed9321e64dc208fe95a

▼ファイルの変更履歴を行単位（orコミット単位）で調べる方法
http://qiita.com/shuntaro_tamura/items/b8b99a9b4987144581e9

▼コミットの取り消し、打ち消し、上書き
http://qiita.com/shuntaro_tamura/items/06281261d893acf049ed

▼ブランチ切って更新してマージするまでの流れ
http://qiita.com/shuntaro_tamura/items/6c8bf792087fe5dc5103

▼初心者のためのgitコマンド
http://qiita.com/shuntaro_tamura/items/5228a29082a844d4875c

▼【Gitおすすめサイト1】サルでもわかるGit入門
http://www.backlog.jp/git-guide/

▼【Gitおすすめサイト2】Git公式ドキュメント
http://git-scm.com/docs/