機能を作っている途中で「急ぎのバグ修正を先にやってほしい」と言われたり、別のブランチの内容を確認したくなったりすることがあります。ところが編集中のファイルを抱えたままブランチを切り替えようとすると、Git に止められてしまいます。かといって、中途半端な状態でコミットするのも気が引けます。
こういうときに使うのが git stash です。作業中の変更を一時的に棚に上げて、作業ツリーを最後のコミットの状態に戻してくれます。用事が済んだら、退避しておいた変更を元に戻すだけです。
この記事では、git stash の基本的な退避と復元から、メッセージを付けた保存、pop と apply の違い、未追跡ファイルや特定のファイルだけを退避する方法、そして退避した内容が戻せなくなったときの対処まで解説します。
目次
ブランチを切り替えようとして怒られたときが出番
まずは git stash が必要になる場面を見ておきます。style.css を編集している最中に main ブランチへ切り替えようとすると、次のように止められます。
$ git checkout main
error: Your local changes to the following files would be overwritten by checkout:
style.css
Please commit your changes or stash them before you switch branches.
Aborting
切り替え先のブランチで同じファイルの内容が違うため、そのまま切り替えると編集中の内容が上書きされて消えてしまいます。それを防ぐために Git が処理を中断しているわけです。メッセージにも「コミットするか stash しなさい」と書かれています。
同じことは git pull のときにも起きます。リモートの変更を取り込もうとしたら、編集中のファイルが衝突して Please commit your changes or stash them before you merge. と言われる、という流れです。どちらも「いったん作業を片付けてから来てください」という意味で、その片付け役が git stash です。
git stash の基本
引数なしで git stash を実行すると、作業ツリーの変更とステージした内容をまとめて退避し、作業ツリーを HEAD(最後のコミット)の状態に戻します。
# 変更中のファイルがある状態
$ git status --short
M app.js
M style.css
# 退避する
$ git stash
Saved working directory and index state WIP on main: 28f9e36 最初のコミット
# 作業ツリーがきれいになった
$ git status --short
$
この状態ならブランチの切り替えも git pull も問題なく通ります。退避した内容は消えたわけではなく、git stash list で一覧できます。
$ git stash list
stash@{0}: WIP on main: 28f9e36 最初のコミット
stash@{0} が退避した内容に付けられた名前です。stash は後入れ先出しのスタックになっていて、新しく退避したものが常に stash@{0} になり、それまでの stash@{0} は stash@{1} へと押し下げられます。WIP on main は自動で付けられた説明で、どのブランチのどのコミットの上で退避したかを表しています。
用事が済んだら git stash pop で元に戻します。
$ git stash pop
On branch main
Changes not staged for commit:
modified: app.js
modified: style.css
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (d1726ba521962e0adc790d4227380dbbde8ecc01)
編集中だった内容が作業ツリーに戻り、最後の Dropped の行が示すとおり、取り出した stash は一覧から削除されます。退避して、切り替えて、戻す。これが git stash のいちばん基本的な流れです。
メッセージを付けて退避する
stash を1つしか使わないうちは困りませんが、いくつも溜まってくると WIP on main ばかりが並んで区別が付かなくなります。そこで、退避するときに git stash push -m で説明を付けておきます。
$ git stash push -m "ヘッダーの色を調整中"
Saved working directory and index state On main: ヘッダーの色を調整中
$ git stash list
stash@{0}: On main: ヘッダーの色を調整中
git stash は git stash push の省略形なので、この2つは同じ動作をします。オプションを付けたいときは push を書く、と覚えておけば十分です。なお、古い記事では git stash save "メッセージ" という書き方を見かけますが、save は非推奨になっているので、これから書くなら push -m を使ってください。
退避するのは一瞬でも、取り出すのは翌日や来週ということが普通にあります。自分が何を途中で止めたのかは驚くほど早く忘れるので、メッセージは面倒でも付けておくことをおすすめします。
退避した中身を確認する
取り出す前に中身を見たいときは git stash show を使います。引数なしだと、どのファイルが何行変わったかの要約だけが表示されます。
$ git stash show
app.js | 2 +-
style.css | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
-p(--patch)を付けると、差分そのものを確認できます。
$ git stash show -p
diff --git a/style.css b/style.css
index 991912d..7614dc7 100644
--- a/style.css
+++ b/style.css
@@ -1 +1 @@
-body { color: red; }
+body { color: blue; }
引数を省略すると stash@{0}(いちばん新しい退避)が対象になります。別のものを見たいときは名前を渡します。
$ git stash list
stash@{0}: On main: ヘッダーの色を調整中
stash@{1}: On main: フォーム周りの試作
# 2番目の中身を見る
$ git stash show -p "stash@{1}"
# 番号だけでも同じ意味になる
$ git stash show -p 1
シェルによっては波かっこがそのまま解釈されないことがあるので、"stash@{1}" のように引用符で囲むか、番号だけを渡す書き方が確実です。この指定方法は show だけでなく、このあと出てくる pop や apply、drop でも共通して使えます。
pop と apply の違い
退避した内容を作業ツリーに戻すコマンドは2つあります。どちらも「変更を復元する」点は同じですが、復元したあとに stash を残すかどうかが違います。
pop は取り出して一覧から消す
git stash pop は、変更を復元したうえで、その stash を一覧から削除します。スタックから「取り出す」操作なので、成功すれば git stash list からその行が消えます。
$ git stash pop
(変更が復元される)
Dropped refs/stash@{0} (d1726ba521962e0adc790d4227380dbbde8ecc01)
$ git stash list
$
apply は復元しても残したままにする
git stash apply は変更を復元するだけで、stash は一覧に残ります。
$ git stash apply
(変更が復元される。Dropped の行は出ない)
$ git stash list
stash@{0}: On feature: 緑に変更中
残っているということは、同じ内容を何度でも復元できるということです。そのため、同じ変更を複数のブランチに当てたいときに向いています。たとえばデバッグ用の一時的な設定変更を退避しておき、検証したいブランチそれぞれで apply する、といった使い方です。
迷ったら apply、確認できたら drop
普段の「ブランチを切り替えて戻ってきた」という場面では pop で構いません。一覧が勝手に片付くので、使い終わった stash が溜まらずに済みます。
一方で、復元先のブランチが退避したときと変わっている場合や、大きな変更を長く預けていた場合は apply のほうが安全です。apply なら、うまく当たらなかったときや、当てた結果が思っていたものと違ったときに、作業ツリーを元に戻してやり直せます。内容に問題がないと確認できてから git stash drop で消す、という手順が確実です。
# まず当ててみる
$ git stash apply
# 内容を確認して、問題なければ消す
$ git stash drop
Dropped refs/stash@{0} (da9364548a64d34079532663803847f7da94be0a)
未追跡ファイルは既定では退避されない
git stash が退避するのは、Git が管理している(追跡済みの)ファイルの変更だけです。新しく作ったばかりでまだ git add していないファイル、つまり git status で Untracked files に並ぶファイルは対象になりません。
$ git status --short
M app.js
M style.css
?? memo.txt ← 新規作成したファイル
$ git stash
Saved working directory and index state WIP on main: 28f9e36 最初のコミット
$ git status --short
?? memo.txt ← 退避されずに残っている
新しく追加したファイルまで含めて作業ツリーをきれいにしたいときは、-u(--include-untracked)を付けます。
$ git stash push -u -m "未追跡ファイルも含めて退避"
Saved working directory and index state On main: 未追跡ファイルも含めて退避
$ git status --short
$ ← memo.txt も消えて完全にきれいになった
この状態で git stash pop すれば、memo.txt もファイルごと戻ってきます。
ただし -u でも、.gitignore で無視しているファイルは対象外です。node_modules やビルド成果物まで含めたい場合は -a(--all)を使いますが、大量のファイルを巻き込んで時間もディスクも消費するので、通常の作業で使う場面はほとんどありません。
一部のファイルだけ退避する
git stash push のあとにパスを書くと、そのファイルの変更だけを退避できます。「CSS の調整は残したまま、JavaScript 側の実験だけいったん引っ込めたい」というときに便利です。
$ git status --short
M app.js
M style.css
# style.css の変更だけを退避する
$ git stash push -m "CSSだけ退避" -- style.css
Saved working directory and index state On main: CSSだけ退避
$ git status --short
M app.js ← app.js の変更は手元に残っている
パスの前に -- を書いているのは、ファイル名をオプションと区別するためです。省略しても動くことが多いのですが、ファイル名がハイフンで始まる場合などに誤解釈されるので、付ける習慣にしておくと安全です。ディレクトリ単位や src/*.js のようなパターンも指定できます。
不要になった退避を削除する
結局使わなかった stash は git stash drop で削除します。引数を省略すると stash@{0} が対象になるので、消したいものが別にある場合は名前を渡してください。
# 最新の退避を削除
$ git stash drop
Dropped refs/stash@{0} (393e6f546ba1482ef18195e93f2f9f70272b0389)
# 2番目の退避を削除
$ git stash drop "stash@{1}"
1つ消すと番号が繰り上がるので、複数まとめて消すときは番号の大きいほうから処理するか、そのつど git stash list で確認し直してください。
全部まとめて捨てたい場合は git stash clear です。ただしこのコマンドは確認を挟まず、一覧にあるすべての退避を消します。中身が必要かどうか怪しいものが混ざっているなら、実行する前に git stash list と git stash show -p で目を通しておくことを強くおすすめします。
退避した変更から新しいブランチを作る
git stash branch は、指定した名前で新しいブランチを作り、そこへ stash の内容を復元してくれるコマンドです。
$ git stash branch feature/new-ui "stash@{0}"
Switched to a new branch 'feature/new-ui'
On branch feature/new-ui
Changes not staged for commit:
modified: a.txt
no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{0} (e4a13502320c3cde1652e205dab59906d5c6c4e6)
$ git stash list
$ ← 復元に成功したので stash は削除されている
ポイントは、新しいブランチがその stash を作ったときのコミットから分岐することです。退避したあとに元のブランチが何コミットも進んでいても、その影響を受けずに、退避した瞬間とまったく同じ土台の上で作業を再開できます。
そのため、「main で作業してしまったのでブランチを切り直したい」というときにも使えます。git stash で退避してから git stash branch を実行すれば、専用のブランチに移したうえで作業を続けられます。あとで説明するコンフリクトに悩まされにくいのも利点です。
コマンドとオプションの一覧
ここまでに出てきたサブコマンドをまとめます。<stash> の部分には "stash@{1}" や 1 を指定でき、省略すると stash@{0} が対象になります。
| コマンド | 説明 |
|---|---|
git stash | git stash push の省略形。追跡済みファイルの変更とステージ内容を退避する |
git stash push -m "メッセージ" | 説明を付けて退避する |
git stash list | 退避した一覧を表示する |
git stash show [<stash>] | 退避の内容を要約(変更されたファイルと行数)で表示する |
git stash show -p [<stash>] | 退避の内容を差分(パッチ)として表示する |
git stash pop [<stash>] | 退避を復元し、成功したら一覧から削除する |
git stash apply [<stash>] | 退避を復元する。一覧には残したままにする |
git stash drop [<stash>] | 指定した退避を1つ削除する |
git stash clear | 退避をすべて削除する(確認なし) |
git stash branch <ブランチ名> [<stash>] | 退避した時点のコミットから新しいブランチを作り、そこへ復元する |
git stash push に付けられる主なオプションは次のとおりです。
| オプション | 説明 |
|---|---|
-m <メッセージ> | 退避に説明を付ける(--message) |
-u | 未追跡ファイルも退避する(--include-untracked) |
-a | 未追跡ファイルに加えて、無視されているファイルも退避する(--all) |
--keep-index | ステージ済みの内容は作業ツリーに残したまま退避する |
-- <パス> | 指定したファイル・ディレクトリの変更だけを退避する |
stash が戻せないときの対処
git stash でつまずくのは、たいてい「退避するとき」ではなく「戻すとき」です。よくある3つのケースを見ていきます。
pop でコンフリクトが起きた
退避したあとに同じ箇所が別のコミットで書き換わっていると、復元がマージ扱いになり、コンフリクトすることがあります。
$ git stash pop
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
On branch main
Unmerged paths:
both modified: a.txt
The stash entry is kept in case you need it again.
ここで慌てる必要はありません。最後の行にあるとおり、コンフリクトした場合の pop は stash を削除しません。git stash list を見ればまだ残っているので、退避した内容が失われることはないからです。
対処としては、通常のコンフリクトと同じように <<<<<<< で囲まれた箇所を編集して解決し、git add します。解決したあとも stash は残ったままなので、不要になったことを確認してから git stash drop で自分で消してください。
解決するのが面倒で、いったん復元前の状態に戻したい場合もあります。ただし、コンフリクト中のファイルは未解決(unmerged)の状態になっているため、git checkout -- . では error: path 'a.txt' is unmerged と言われて戻せません。git reset --hard で作業ツリーごと HEAD の状態に戻してください。stash は残っているので、あとで何度でもやり直せます。
drop や clear で消してしまった
git stash drop や git stash clear を実行すると一覧からは消えますが、実は中身のコミット自体はしばらくリポジトリに残っています。参照されなくなった(到達不能な)コミットを探せば、拾い直せる可能性があります。
# 到達不能なコミットを探す
$ git fsck --unreachable | grep commit
unreachable commit 519f6dfcf90605f3383ac449e1a4d1e43f2902ed
unreachable commit 393e6f546ba1482ef18195e93f2f9f70272b0389
# どれが目的の stash かをコミットメッセージで見分ける
$ git show --no-patch --format="%s" 519f6df
index on main: d116f65 初回
$ git show --no-patch --format="%s" 393e6f5
On main: 消してしまった作業 ← これ
# ハッシュを直接指定して復元する
$ git stash apply 393e6f5
1回の退避につき複数のコミットが作られるため、候補は必ず複数出てきます。メッセージが index on ... で始まるものはステージ内容を記録した側のコミットで、これを指定すると is not a stash-like commit というエラーになります。On <ブランチ名>: や WIP on <ブランチ名>: で始まるほうを選んでください。
なお、到達不能なコミットは git gc が走ると回収されて本当に消えます。消したことに気づいたら、できるだけ早く探すのが鉄則です。そして、そもそもこの作業をしなくて済むように、clear は中身を確認してから使うようにしてください。
新規作成したファイルが戻ってこない
「退避して pop したのに、新しく作ったファイルがない」という場合は、そもそも退避されていなかった可能性が高いです。前述のとおり、git stash は未追跡ファイルを対象にしません。-u を付けずに退避したなら、そのファイルは stash に入らず作業ツリーに残ったままだったはずです。
問題は、そのあとブランチを切り替えたり、別の作業をしたりする過程で、置き去りになったファイルを git clean などで消してしまうケースです。git clean -fd は未追跡ファイルを実ファイルごと削除するコマンドで、Git の管理外なので復元手段がありません。新規ファイルを含む作業を退避するときは、git stash push -u を使う癖を付けておくと事故を防げます。
まとめ
git stash は、コミットするほどではない作業中の変更を一時的に預けて、作業ツリーをきれいにするためのコマンドです。git stash push -m "メッセージ" で退避し、git stash list で確認し、git stash pop で戻す。この3つを覚えるだけで、ブランチの切り替えや git pull で止められる場面はほぼ解決します。
pop は復元して一覧から消し、apply は残したままにします。同じ変更を複数のブランチに当てたいときや、うまく当たるか自信がないときは apply を使い、内容を確認してから git stash drop で削除するのが安全です。新規作成したファイルを含めたいときは -u、一部のファイルだけ預けたいときはパス指定を使い分けてください。
stash はあくまで一時置き場です。長く預けたままにすると、元のブランチが進んでコンフリクトの原因になりますし、内容も忘れます。数日以上かかりそうな作業は、git stash branch で専用のブランチに移すか、素直に作業途中のコミットを作ってしまうほうが扱いやすくなります。