コミット前の内容を一時的に避けておく「git stash」の使い方

Gitで必ず使う基本の操作「コミット」。慣れてくるとログの状態にも気が回るようになり、なるべくきれいにコミットしたくなってきます。

でも、時には作業途中に急遽別作業に移らなければならないケースもあったりして、「ブランチ移動の際にコミットを要求される。でもできればまだコミットはしたくない。かといって今の作業内容を捨てたくもない」という状況がしばしばあります。

そんなときは、コミット前の内容を一時的に退避させておけるgit stashが役立ちます。

git stashとは?

stashという単語には、” こっそりしまう、隠す “といった意味があります。
今はコミットしたくない変更を一旦しまっておく…というのが目的ですが、もう少し具体的にシーンを挙げてみます。

合わせて、実際のコマンドの流れも見ていきましょう。
ここではgit stashを使うときに必ず知っておきたい、2つのコマンドをご紹介します。

  • git stash … ワーキングツリー内の変更内容を退避する
  • git stash apply … 退避した内容を今いるブランチに適用する

想定シーン1:作業中だけど別のブランチに切り替えたい

冒頭で挙げたような「今の作業よりも優先度が高い作業依頼が来た」ときや、「プルリクエストが来たので自分の作業は一旦置いといて、別のブランチをチェックアウトしたい」ときなどなど、とにかく今の作業を中断して別ブランチに切り替えたい場面。

こんなときがgit stashの出番です。
変更を一時的に避けておき、自分のブランチに戻ってきたときに復活させます。

# 変更を避けておく
$ git stash
# ブランチを切り替えるなり、他の作業をするなりやりたいことをやる
# ...
# 元の作業ブランチに戻ってきた
# 先ほど避けたスタッシュを今いるブランチに適用する
$ git stash apply

これで、ブランチを切り替えるためだけに中途半端な状態をコミットしなくてもOKです!

想定シーン2:作業ブランチを間違えたまま作業を進めてしまった

作業を進めていていざコミット…と思ったらブランチ間違いに気づいた、というシーンです。
コミットしないままブランチの切り替えに成功すれば良いのですが、切り替え先と作業対象が被ってコンフリクトする場合などエラーが出て切り替えられないこともよくあります。

# コミットせず本来のブランチに切り替えようとしたらエラーが出た
$ git checkout work/proper-branch
error: Your local changes to the following files would be overwritten by checkout:
    xxx.html
Please commit your changes or stash them before you switch branches.
Aborting

こんなときも、git stashコマンドで解決です。

# 変更を避けて正しいブランチに移動
$ git stash
$ git checkout work/proper-branch
# 先ほど避けたスタッシュを今いるブランチに適用する
$ git stash apply

余談:そもそも既にコミットしてしまったときは?

上記の例で、ブランチ間違いに気付かないままコミットしちゃったよ!というとき。
対処法は色々ありそうですが、あえてstashを使うとすると下記のような解決方法が考えられます。

# 直前のコミットを取り消して、コミット前の状態に戻す
$ git reset --soft HEAD^
# (ここからは先ほどと同じ)
# 変更を避けて正しいブランチに移動
$ git stash
$ git checkout work/proper-branch
# 先ほど避けたスタッシュを今いるブランチに適用する
$ git stash apply

関連コマンドをさらに詳しく

git stashに関するコマンドやオプションは他にも色々あります。
特に新規追加したファイルを避けておきたい場合は、オプションつきでの実行が必要なので要注意です。目的別により詳しくコマンドを見ていきましょう。

変更内容の保存

先ほど紹介した基本のコマンド、git stashです。
実行後はメッセージが出て、WIP on {作業ブランチ名}: {親コミットID} {親コミットメッセージ}という名称でスタッシュが保存されることが確認できます。

$ git stash
# 実行後
Saved working directory and index state WIP on {作業ブランチ名}: {親コミットID} {親コミットメッセージ}

または、自分でわかりやすいようにコメントをつけることも可能です。

$ git stash save "{コメント}"
# 実行後
Saved working directory and index state On {作業ブランチ名}: {コメント}

【注意】 新規ファイルを退避対象に含めたい場合

git stashを実行したとき、デフォルトではワーキングツリー内の「新規ファイルの追加」は退避対象に含まれません。 下記のように「(1)オプションをつけて実行」するか、「(2)新規ファイルをaddしてから実行」しましょう。

# 新規ファイルの追加だけしてgit stashしたら「保存する変更がない」と言われた
No local changes to save
# (1)オプションをつけて実行
$ git stash -u
# (2)新規ファイルをaddしてから実行
$ git add {新規ファイル名}
$ git stash

保存したスタッシュを確認する

保存されている内容のリストを表示するにはgit stash listを使います。

# 一覧を表示
$ git stash list
# 実行後
stash@{0}: WIP on work/branch3: 789ijkl third commit
stash@{1}: WIP on work/branch2: 456efgh second commit
stash@{2}: WIP on work/branch1: 123abcd first commit

stash@{0}がスタッシュの管理番号で、コマンド実行時に対象のスタッシュを指定する場合にはこれを使います。
ただしこの番号はユニークなものではなく、新しいものがどんどん上に(stash@{0}に)追加されていく点を覚えておきましょう。

今いるブランチにスタッシュを適用する

先ほど、スタッシュの適用にはgit stash applyを紹介しました。
これを詳細に解説すると、「今いるブランチに直近のスタッシュを適用」かつ「スタッシュ自体は消さずに残しておく」というコマンドになります。

適用後スタッシュを削除したいときはgit stash popです。

# 直近のスタッシュ(stash@{0})を現在のブランチに適用
$ git stash apply
# 直近のスタッシュ(stash@{0})を現在のブランチに適用 + スタッシュを消す
$ git stash pop

また、デフォルトでは直近のスタッシュ(stash@{0})が適用されます。
特定のスタッシュを指定したい場合は、先述のgit stash listで番号を確認し、apply(もしくはpop)に続けて指定してください。

$ git stash apply stash@{1}

スタッシュを削除する

退避した変更内容が不要になったら、git stash dropもしくはgit stash clearで削除できます。

# 直近のスタッシュ(stash@{0})を削除
$ git stash drop
# 番号を指定
$ git stash drop stash@{1}
# すべて削除
$ git stash clear

あとがき

慣れてきたらコミットをまとめてPull Requestしよう(git merge –squash)を覚えてから統合ブランチはもとより、自分用の作業ブランチのコミット粒度もある程度は気にしていけたら…と思うようになりました。今回ご紹介したような急なシーンでは、git stashを使うと「とりあえずコミット」しなくてもよくなるのでスッキリします:)

逆に言うと、git stashはあくまで一時的な退避だけを目的に利用するのがおすすめです。
手軽に蓄積できる一方で煩雑にもなりやすく、また誰かと共有できるものでもないので、それなりに作業から離れるようなときはコミットを使う方が安心です。

多少中途半端になってしまっても、コミットは後からまとめたり書き換えたり、比較的自由に操作ができます。柔軟に使い分けていきましょう!