git stash — 書きかけを退避する
書きかけの変更があると、checkout や revert は止まります。まだコミットするほどではないけれど、捨てたくもない。そういう書きかけを、いったん退避の棚にしまって作業ツリーをきれいにするのが git stash です。この回では、退避してブランチを乗り換え、用事を済ませて戻ってくる流れを見てから、棚の中身の扱い方(list・pop・apply・drop)と、stash が退避しないものを確かめます。
- 上のターミナルは、実際に打ったときと同じ出力です(Git は英語で答えます)
- 下の中で起きていることは、コマンドの結果、Git の中の何が変わったかを図にしたものです。丸がコミット、黒い札がブランチ、ピンクの札が HEAD です
- 退避した書きかけは、右下の退避(stash)の棚に積まれます
- 右の説明をスクロールして読み進めると、左のターミナルでコマンドが1つずつ打たれます。
→キーでも1つずつ進みます(←で1つ戻ります。スマホでは「次へ」ボタン)。最後まで進んでから→を押すと次の区間へ移ります。黄色く光っているのが、そのコマンドで変わった場所です
1. git stash — 書きかけをしまって、乗り換える
checkout の回では、書きかけが上書きされるときに checkout が止まるのを見ました。そのときのエラー文には Please commit your changes or stash them(コミットするか、stash してください)と出ていました。この場面の続きを、stash でやってみます。
git stash がすることは2つです。
- 作業ツリーとステージの書きかけを、退避の棚に積む
- 作業ツリーとステージを、HEAD のコミットの中身に戻す
stash で退避してブランチを乗り換える
checkout の回の続きです。
mainでtodo.txtに書きかけの1行があります。$ echo "チーズを買う" >> todo.txtfeatureに移ると書きかけが上書きされるので、checkout は止まります。まだコミットするほどではない書きかけを、どうするかです。$ git checkout feature error: Your local changes to the following files would be overwritten by checkout: todo.txt Please commit your changes or stash them before you switch branches. Abortinggit stashは、書きかけの変更を 退避の棚 にしまい、作業ツリーとステージを HEAD のコミットの中身に戻します。右下の棚に1つ入りました。WIP on mainは「mainで作業中だったもの」という意味です。$ git stash Saved working directory and index state WIP on main: 71d9b3e バターを追加作業ツリーがきれいになりました。書きかけは消えたのではなく、棚にしまってあります。
$ git status On branch main nothing to commit, working tree clean今度は乗り換えられます。退避の棚はブランチとは別の場所なので、乗り換えても棚の中身はそのままです。
$ git checkout feature Switched to branch 'feature'featureでの用事を済ませます。$ echo "ジャムを買う" >> todo.txtfeatureが1つ進みました。$ git commit -am "ジャムを追加" [feature bee85e4] ジャムを追加 1 file changed, 1 insertion(+)mainに戻ります。作業ツリーは「バターを追加」の中身で、まだ書きかけはありません。$ git checkout main Switched to branch 'main'git stash popで、棚の一番上の書きかけを作業ツリーに戻します。戻したら、その退避は棚から消えます(Dropped)。$ git stash pop On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: todo.txt no changes added to commit (use "git add" and/or "git commit -a") Dropped refs/stash@{0} (2cb140787ab31c261e146db1c3475be5ef684c18)「チーズを買う」が戻ってきました。退避した
mainに戻ってから pop したので、ぶつかるものはありません。$ cat todo.txt 牛乳を買う 卵を買う パンを買う バターを買う チーズを買う
流れをまとめると、stash → 乗り換える → 用事を済ませて戻る → stash pop です。退避の棚はブランチとは別の場所にあるので、どのブランチに乗っていても同じ棚が見えます。戻すときは、退避したブランチに戻ってから pop すると、ぶつかるものがなく確実です。
2. 名前を付けて積む — list・pop・apply・drop
棚には退避をいくつでも積めます。git stash は git stash push と同じ意味で、-m "名前" を付けると、あとで見分けやすい名前が付きます。名前を付けないと WIP on main: …(どのブランチの、どのコミットの上で作業中だったか)が名前になります。
| コマンド | 何をするか | 棚の退避 |
|---|---|---|
git stash / git stash push -m "名前" | 書きかけを棚に積み、作業ツリーをきれいにする | 1つ増える |
git stash list | 棚の中身を一覧する。stash@{0} が一番新しい | そのまま |
git stash pop | 退避を作業ツリーに戻す | 戻したものは消える |
git stash apply | 退避を作業ツリーに戻す | 残る |
git stash drop | 退避を戻さずに捨てる | 消える |
番号を省くと、pop・apply・drop は stash@{0}(一番新しいもの)を対象にします。古いものを選ぶときは git stash apply stash@{1} のように番号を書きます。
名前を付けて退避する — list・apply・drop・pop
書きかけを2つ、別々に退避してみます。まず
todo.txtに1行足します。$ echo "チーズを買う" >> todo.txt-mで退避に名前を付けられます。git stash pushはgit stashと同じ意味で、-mなどのオプションを付けるときに使う形です。$ git stash push -m "チーズの件" Saved working directory and index state On main: チーズの件続けて、今度は
notes.txtに1行足します。$ echo "10時に出る" >> notes.txtpushを省いてgit stash -mと書いても同じです。棚に2つ積まれました。$ git stash -m "時間の件" Saved working directory and index state On main: 時間の件git stash listで棚の中身が出ます。stash@{0}が一番新しい 退避で、古いものほど番号が大きくなります。$ git stash list stash@{0}: On main: 時間の件 stash@{1}: On main: チーズの件applyは、番号で選んだ退避を作業ツリーに戻します。ただし 棚には残したまま です。$ git stash apply stash@{1} On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: todo.txt no changes added to commit (use "git add" and/or "git commit -a")「チーズの件」はまだ棚にあります。戻した中身と棚の中身が、両方ある状態です。
$ git stash list stash@{0}: On main: 時間の件 stash@{1}: On main: チーズの件戻したチーズはコミットしました。
$ git commit -am "チーズを追加" [main aac69a0] チーズを追加 1 file changed, 1 insertion(+)もう要らない「チーズの件」は
dropで棚から捨てます。drop は退避を作業ツリーに戻さずに捨てます。ここではコミット済みなので困りませんが、まだ戻していない退避を drop すると、その書きかけは失われます。$ git stash drop stash@{1} Dropped refs/stash@{1} (595a4542ef99496d2b0a7d2d91081b825f0064fa)残った「時間の件」を
popで戻します。番号を省くとstash@{0}が対象です。pop は「apply して drop する」と同じです。$ git stash pop On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: notes.txt no changes added to commit (use "git add" and/or "git commit -a") Dropped refs/stash@{0} (45a6ff6951e3a8413a7569273def0e24e62f5d63)棚が空になったので、もう一度 pop すると
No stash entries found.で止まります。$ git stash pop No stash entries found.
pop は「apply して drop する」と同じです。迷ったら apply で戻して、うまく戻ったのを確かめてから drop する、という使い方もできます。
3. stash が退避しないもの・戻さないもの
stash には、知らないと戸惑う既定の動きが2つあります。
| 場面 | 既定の動き | 変えたいとき |
|---|---|---|
| 一度も add していない(追跡していない)ファイルがある | git stash は退避しない。作業ツリーに残る | git stash -u(--include-untracked)で一緒に退避する |
| add してステージに入れた変更を退避した | git stash pop は、作業ツリーの変更として戻す。ステージには戻らない | git stash pop --index でステージも戻す |
stash が退避しないもの・戻さないもの
todo.txtに1行足します。$ echo "パンを買う" >> todo.txtこれは add して、ステージにも入れておきます。
$ git add todo.txtもう1つ、新しいファイル
memo.txtを作ります。こちらは一度も add していない、追跡していないファイル です。$ echo "レシピを見る" > memo.txt退避します。棚に入ったのは
todo.txtの変更だけです。$ git stash Saved working directory and index state WIP on main: 8b41d07 卵を追加memo.txtはUntracked filesに残ったままです。git stashは、追跡していないファイルを退避しません。作業ツリーがきれいになったように見えて、実は残っています。$ git status On branch main Untracked files: (use "git add <file>..." to include in what will be committed) memo.txt nothing added to commit but untracked files present (use "git add" to track)戻します。
todo.txtはChanges not staged for commitに出ました。退避する前は add してあったのに、ステージには戻らず、作業ツリーの変更として戻る のが既定の動きです。$ git stash pop On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: todo.txt Untracked files: (use "git add <file>..." to include in what will be committed) memo.txt no changes added to commit (use "git add" and/or "git commit -a") Dropped refs/stash@{0} (1542b74d0ecb707c2476eb32c3c970b71655db9a)ステージに入れたいときは、もう一度 add します。
$ git add todo.txt退避する前と同じ状態に戻りました。
$ git status On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: todo.txt Untracked files: (use "git add <file>..." to include in what will be committed) memo.txt
1つ目は、stash したのに作業ツリーにファイルが残っているように見える原因になります。git status の Untracked files を見れば、残っているのが追跡していないファイルだと分かります。2つ目は、戻したあとに add し直せば済みます(新しく add したばかりのファイルだけは、ステージに載った状態で戻ります)。
なお、この教材の画面は -u と --index を再現していません。どちらも本物の Git では使えます。
4. pop がぶつかるとき
退避したあとで、同じファイルが別のコミットで変わっていることがあります。そのまま pop すると、Git は行単位で合わせにいき、近くの行がぶつかるとコンフリクトになって途中で止まります。このとき退避は棚から消えずに残り、Git は The stash entry is kept in case you need it again.(必要になったときのために退避を残しています)と知らせます。
また、戻す先のファイルにまだ書きかけがあると、pop は何もせずに止まります。checkout や revert と同じく、未コミットの変更を勝手に上書きしないためです。コンフリクトの直し方は、第3部の merge の回で扱います。
5. Claude Code と stash
Claude Code が実際に打っている stash は、git stash list と git stash pop が中心です。git stash list は棚を見るだけのコマンドで、作業を始める前に「退避したまま忘れている書きかけがないか」を確かめるのに使います。
もう1つよく出てくるのが git rebase --autostash です。rebase も書きかけがあると止まるので、--autostash を付けると、始める前に自動で stash し、終わったら自動で戻します。中でやっていることは、この回の stash と pop です。rebase そのものは第3部で扱います。
6. Claude Code の許可画面で見かけたら
この回で見たコマンドを、「何が消えうるか」で並べるとこうなります。
| コマンド | 何が起きるか | 消えうるもの |
|---|---|---|
git stash list | 棚を見るだけ | なし |
git stash / git stash push -m "名前" | 書きかけを棚に積み、作業ツリーとステージを HEAD の中身に戻す | なし(追跡していないファイルは退避されず、作業ツリーに残る) |
git stash apply | 退避を作業ツリーに戻す。棚にも残す | なし(上書きされる書きかけがあれば止まる) |
git stash pop | 退避を作業ツリーに戻し、棚から消す | なし(ステージに入れていた区別は戻らない。ぶつかったら退避は残る) |
git stash drop | 退避を戻さずに捨てる | 注意 まだ戻していなければ、その書きかけ |
git stash clear | 棚を空にする | 棚の退避すべて |
見分け方は、退避を作業ツリーに戻さずに棚から消すかどうかです。drop や clear を許可する前に、git stash list でその退避が本当に要らないものかを確かめてください。
まとめ
git stash(=git stash push)は、書きかけを退避の棚に積み、作業ツリーとステージを HEAD の中身に戻す- 書きかけで checkout が止まったら、stash → 乗り換える → 用事を済ませて戻る → stash pop
git stash listのstash@{0}が一番新しい。-mで名前を付けられる- pop は戻して棚から消す。apply は戻して棚に残す。drop は戻さずに捨てる
- 一度も add していないファイルは退避されない。pop すると、ステージにあった変更も作業ツリーの変更として戻る