サクッと使いこなすためのgit stash Tips & stashの仕組み

便利ですよね、stash。
普通に使ってるだけでも便利なんですが、知っておくと更にサクッと使いこなせるTipsをQ&A形式で紹介します。
おまけとして、Tipsで紹介したオプションを実現するstashの仕組みにもチラっと触れています。

# git stash Tips

## Q. add済みなファイルだけゴニョゴニョしたいからnot stagedなファイルだけを退避しておきたい
## A. stash --keep-index (-k) オプションを使う
今の状況 ： add済みなファイルだけをゴニョゴニョしたいのにnot stagedなファイルがあって邪魔、ちょっと消えてて欲しい。

```
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   bar
#
# 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:   hoge
#
```

普通にstashすると両方退避してしまうが、--keep-index (-k) オプションを使うと

```zsh
$ git stash -k
```

```
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   bar
#
```

not stagedなファイルだけ退避されてindexエリアのファイルは残る。

## Q. untrackedなファイルも退避したい
## A. stash --include-untracked (-u) オプションを使う
今の状況 : なんかごちゃごちゃしてきた。一時的にクリーンな状態にしたい。

```
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   bar
#
# 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:   hoge
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       foo
```

--include-untracked (-u) オプションを使うと

```zsh
$ git stash -u
```

```
$ git status
# On branch master
nothing to commit, working directory clean
```

untrackedファイルも退避してクリーンな状態になる。

## Q. ignoredファイルも含め、完っ全にクリーンな状態にしたい
## A. stash --all (-a) オプションを使う
今の状況 : ```git status```はクリーンな状態なんだけど、ignoredファイルがいっぱいあって、ディレクトリ内の構成が一致しない・・・

```
$ git status
# On branch master
nothing to commit, working directory clean
```

```
$ git ls-files
bar
hoge
```

```git status```はクリーンで、リポジトリで管理しているファイルは```bar```と```hoge```だけだが

```zsh
$ ls -a
.  ..  .DS_Store  .Trashes  .directory  .git  bar  hoge  tags
```

ディレクトリ内に除外ファイルがいっぱいあって邪魔。一時的に除外ファイルも退避させて完全にクリーンな状態にしたい。

--all (-a) オプションを使うと

```zsh
$ git stash -a
```

```zsh
$ ls -a
.  ..  .git  bar  hoge
```

通常のstashに加え、untracked, ignoredファイルも退避され、一時的にリポジトリが完全にクリーンな状態になる。


## Q. stashしてpopしたらaddしていたファイルがnot stagedになった
## A. pop --index オプションを使う
今の状況 : gitで管理しているhogeとbarファイルがあり、barの変更はaddされている。退避させたいが、stashしてpopしたら変更が消えてしまう・・・

```
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   bar
#
# 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:   hoge
#
```

```zsh
$ git stash
$ git stash pop
```
すると・・・

```
$ 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:   bar
#       modified:   hoge
#
no changes added to commit (use "git add" and/or "git commit -a")
```

勝手にunstageされている・・・

--indexオプションを使うと

```zsh
$ git stash
$ git stash pop --index
```

```
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   bar
#
# 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:   hoge
#
```

変更が保存されたまま復元できる。

# おまけ - stashの仕組み
ところで、stashはどうしてadd済みのファイルとそうでないファイルを区別できているのでしょうか。
何かマークでもつけているんでしょうか？


gitを使っている人ならstashの仕組みを知らなくてもなんとなく気付いてると思いますが、stashで退避されたファイルもコミットで管理されています。

stash用の新しいブランチを作り
退避するファイルをコミットし
コミットのリビジョンをスタックに積み
・・・
というようなことを自動的にやっています。

```zsh
$ git stash list
stash@{0}: WIP on master: 8b7ca77 initial commit.
```

```git stash list```の一覧で左側に表示される```stash@{0}```は```HEAD```などと同じようにリビジョンの参照です。

stashのコミットログを見てみます。

```
$ git log --graph stash@{0}

*   commit ee5d8bdf21c6dfedb6b5a648241a20eff01b2418
|\  Merge: 8b7ca77 9dcce8a
| | Author: ton <tonton1517@gmail.com>
| | Date:   Sat Oct 19 14:57:25 2013 +0900
| | 
| |     WIP on master: 8b7ca77 initial commit.
| |   
| * commit 9dcce8af3e987391fcd1cdd2be95785b25c04944
|/  Author: ton <tonton1517@gmail.com>
|   Date:   Sat Oct 19 14:57:25 2013 +0900
|   
|       index on master: 8b7ca77 initial commit.
|  
* commit 8b7ca77846392852f4c9b0304b16116369b712ce
  Author: ton <tonton1517@gmail.com>
  Date:   Sat Oct 19 12:07:03 2013 +0900
  
      initial commit.
```

initial commitのみ自分でコミットしたものです。何やら2つコミットされてるようですね。

中身を見てみると、```index on master: ...```とコメントされている方のコミットでindexエリアのファイルをコミットし、
更に新しいブランチでnot stagedなファイルを追加し、
```index on master: ...```コミットと元ブランチのコミットをマージしたものを1つのstashコミットとしているようです。
```git stash list```で表示されるコメントと同じですよね。

別々にコミットしているから区別できるのですね、なるほどー。


ちなみに、untrackedなファイルも含めてstashすると

```
*-.   commit 90c6264e964f3dea2f7d418c49491a360630b387
|\ \  Merge: 8b7ca77 351051a 4ad4d67
| | | Author: ton <tonton1517@gmail.com>
| | | Date:   Sat Oct 19 15:41:51 2013 +0900
| | | 
| | |     WIP on master: 8b7ca77 initial commit.
| | |    
| | * commit 4ad4d67f4a8fd9e31da2f1e49c7f78f7b4db37f1
| |   Author: ton <tonton1517@gmail.com>
| |   Date:   Sat Oct 19 15:41:51 2013 +0900
| |   
| |       untracked files on master: 8b7ca77 initial commit.
| |   
| * commit 351051a076c5e0170acab6047dd15f67ac910fbe
|/  Author: ton <tonton1517@gmail.com>
|   Date:   Sat Oct 19 15:41:51 2013 +0900
|   
|       index on master: 8b7ca77 initial commit.
|  
* commit 8b7ca77846392852f4c9b0304b16116369b712ce
  Author: ton <tonton1517@gmail.com>
  Date:   Sat Oct 19 12:07:03 2013 +0900
  
      initial commit.
```

親を持たないブランチを作りuntrackedなファイルをコミットし、stashコミットにマージされているようです。