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つです。

  1. 作業ツリーとステージの書きかけを、退避の棚に積む
  2. 作業ツリーとステージを、HEAD のコミットの中身に戻す

stash で退避してブランチを乗り換える

  1. checkout の回の続きです。main で todo.txt に書きかけの1行があります。

    $ echo "チーズを買う" >> todo.txt
  2. feature に移ると書きかけが上書きされるので、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.
    Aborting
  3. git stash は、書きかけの変更を 退避の棚 にしまい、作業ツリーとステージを HEAD のコミットの中身に戻します。右下の棚に1つ入りました。WIP on main は「main で作業中だったもの」という意味です。

    $ git stash
    Saved working directory and index state WIP on main: 71d9b3e バターを追加
  4. 作業ツリーがきれいになりました。書きかけは消えたのではなく、棚にしまってあります。

    $ git status
    On branch main
    nothing to commit, working tree clean
  5. 今度は乗り換えられます。退避の棚はブランチとは別の場所なので、乗り換えても棚の中身はそのままです。

    $ git checkout feature
    Switched to branch 'feature'
  6. feature での用事を済ませます。

    $ echo "ジャムを買う" >> todo.txt
  7. feature が1つ進みました。

    $ git commit -am "ジャムを追加"
    [feature bee85e4] ジャムを追加
     1 file changed, 1 insertion(+)
  8. main に戻ります。作業ツリーは「バターを追加」の中身で、まだ書きかけはありません。

    $ git checkout main
    Switched to branch 'main'
  9. 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)
  10. 「チーズを買う」が戻ってきました。退避した 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

  1. 書きかけを2つ、別々に退避してみます。まず todo.txt に1行足します。

    $ echo "チーズを買う" >> todo.txt
  2. -m で退避に名前を付けられます。git stash push は git stash と同じ意味で、-m などのオプションを付けるときに使う形です。

    $ git stash push -m "チーズの件"
    Saved working directory and index state On main: チーズの件
  3. 続けて、今度は notes.txt に1行足します。

    $ echo "10時に出る" >> notes.txt
  4. push を省いて git stash -m と書いても同じです。棚に2つ積まれました。

    $ git stash -m "時間の件"
    Saved working directory and index state On main: 時間の件
  5. git stash list で棚の中身が出ます。stash@{0} が一番新しい 退避で、古いものほど番号が大きくなります。

    $ git stash list
    stash@{0}: On main: 時間の件
    stash@{1}: On main: チーズの件
  6. 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")
  7. 「チーズの件」はまだ棚にあります。戻した中身と棚の中身が、両方ある状態です。

    $ git stash list
    stash@{0}: On main: 時間の件
    stash@{1}: On main: チーズの件
  8. 戻したチーズはコミットしました。

    $ git commit -am "チーズを追加"
    [main aac69a0] チーズを追加
     1 file changed, 1 insertion(+)
  9. もう要らない「チーズの件」は drop で棚から捨てます。drop は退避を作業ツリーに戻さずに捨てます。ここではコミット済みなので困りませんが、まだ戻していない退避を drop すると、その書きかけは失われます。

    $ git stash drop stash@{1}
    Dropped refs/stash@{1} (595a4542ef99496d2b0a7d2d91081b825f0064fa)
  10. 残った「時間の件」を 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)
  11. 棚が空になったので、もう一度 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 が退避しないもの・戻さないもの

  1. todo.txt に1行足します。

    $ echo "パンを買う" >> todo.txt
  2. これは add して、ステージにも入れておきます。

    $ git add todo.txt
  3. もう1つ、新しいファイル memo.txt を作ります。こちらは一度も add していない、追跡していないファイル です。

    $ echo "レシピを見る" > memo.txt
  4. 退避します。棚に入ったのは todo.txt の変更だけです。

    $ git stash
    Saved working directory and index state WIP on main: 8b41d07 卵を追加
  5. 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)
  6. 戻します。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)
  7. ステージに入れたいときは、もう一度 add します。

    $ git add todo.txt
  8. 退避する前と同じ状態に戻りました。

    $ 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 すると、ステージにあった変更も作業ツリーの変更として戻る