git stash: 変更を一時的に退避して、あとで戻す方法
git stash は、コミットしていない変更を別の保管場所に退避し、作業ディレクトリを最後のコミットの状態に戻します。その後、別のタスクに切り替えられます。git stash pop で変更を戻して保管場所から削除し、git stash apply で戻して保管場所に残します。git unstash という専用のコマンドはありません。
git stash が必要になる場面
よくある状況です。タスクの途中でファイルを編集していて、まだコミットするには早い。そこへ、別のブランチの緊急のバグ修正を頼まれました。切り替えようとすると Git に止められるか、変更を失うのが怖い。git stash は、コミットしていないすべての変更を「ポケット」に入れて、作業ディレクトリを最後のコミットの状態に戻します。
$ git stash
Saved working directory and index state WIP on feature/login: 3f2a1c4 Форма входа
$ git status
On branch feature/login
nothing to commit, working tree clean
これで、ブランチを切り替えてバグを直し、元に戻って退避した変更を取り出せます。退避するたびに stash のスタックに1件ずつ記録が作られます。Git はこれを特別なコミットとして保存するので、データは安全で、ブランチに依存しません。
変更を退避する主な方法
説明を付けて退避する
既定の説明「WIP on ブランチ名」は、1週間後には意味を持ちません。説明を付けておきましょう。
git stash push -m "メール検証のフォーム、メールの部分が未完成"
git stash を引数なしで実行するのは、git stash push の短縮形です。古い書き方の git stash save "メッセージ" も動きますが、新しい作業では push -m を使ってください。
新規ファイルも含めて退避する
既定では、stash が持っていくのは追跡されているファイルだけです。一度も git add していない新規ファイルは、作業ディレクトリに残ります。これは意外とよく起きる落とし穴です。
git stash -u # --include-untracked: 追跡されていないファイルも含める
git stash -a # --all: .gitignore のファイルも含める
-a は慎重に使ってください。node_modules、ビルド成果物、ローカルの設定ファイルまで退避されてしまいます。ほとんどの場合は -u で十分です。
一部の変更だけを退避する
git stash push -m "設定" src/config.js # 指定したファイルだけ
git stash -p # 部分ごとに対話的に選ぶ
git stash --staged # インデックスにあるものだけ(Git 2.35 以降)
git stash --keep-index # すべて退避するが、インデックスは作業ディレクトリに残す
--keep-index は、コミット前に役立ちます。コミットに含めるものだけを作業ディレクトリに残し、テストを実行して、そのコミットが単独で正しく動くことを確かめられます。
退避した変更を戻す
git unstash というコマンドは Git にはありません。退避した変更を戻すには、次の2つのコマンドを使います。
| コマンド | 動作 | スタックの記録 |
|---|---|---|
git stash pop | 最新の退避内容を適用する | 競合なしで適用できた場合は削除される |
git stash apply | 最新の退避内容を適用する | 残る |
git stash pop stash@{2} | 特定の記録を適用する | 削除される |
git stash apply --index | インデックスと作業ディレクトリの分けた状態も復元する | 残る |
$ git stash pop
On branch feature/login
Changes not staged for commit:
modified: src/auth.js
modified: src/form.js
Dropped refs/stash@{0} (8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d)
注意してください。既定では、退避時にインデックスにあった変更も、通常の変更として戻ります。インデックスの状態も保ちたい場合は --index を付けます。
pop と apply の使い分け
日常の作業では pop が便利です。取り出して、それで終わり。apply は、同じ変更を複数のブランチに適用したいとき、またはうまくいくか分からないときに選びます。記録が残るので、保険になります。後で git stash drop で削除できます。
退避の一覧を管理する
$ git stash list
stash@{0}: On feature/login: メール検証のフォーム、メールの部分が未完成
stash@{1}: WIP on main: 9b7e0d2 Обновить зависимости
stash@{2}: On feature/api: эксперимент с кэшем
スタックは「後に入れたものが先に出る」仕組みです。stash@{0} は常に最新の記録で、push や drop のたびに番号がずれます。
| コマンド | 用途 |
|---|---|
git stash show | 最新の記録の変更の概要を表示 |
git stash show -p stash@{1} | 特定の記録の差分を全部表示 |
git stash drop stash@{1} | 1つの記録を削除 |
git stash clear | すべての記録を一度に削除 |
git stash branch 名前 stash@{0} | 退避時のコミットからブランチを作り、記録を適用して削除 |
git stash show を既定で概要ではなく差分全体にしたり、未追跡のファイルも表示したりするには、設定 stash.showPatch と stash.showIncludeUntracked を使います。
手順例: 作業の途中で緊急のバグ
- 作業中の変更を、新規ファイルも含めて退避する:
git stash push -u -m "ログイン: 下書き"。 - メインのブランチに移り、修正用のブランチを作る:
git switch main、git pull、git switch -c hotfix/null-email。 - バグを直し、コミットして、レビューに出す。
- 元の作業に戻る:
git switch feature/login。 - 退避した変更を取り出す:
git stash pop。git statusで、すべてあることを確認する。
この場面では、今は git worktree を使う人も多くいます。同じリポジトリに2つ目の作業ディレクトリを作れば、何も退避せずに済みます。ただ、数分だけ切り替えるなら、stash の方が簡単です。
stash を適用するときの競合
退避後にブランチで同じ行が変更されていると、適用時に競合が発生します。
$ git stash pop
Auto-merging src/auth.js
CONFLICT (content): Merge conflict in src/auth.js
The stash entry is kept in case you need it again.
この場合、pop でも記録は削除されません。通常どおりファイルの競合を解決し、git add で解決済みにします(インデックスに入れたくなければ git restore --staged)。そのあと、もう不要になった記録を git stash drop で削除してください。
競合が複雑な場合は、迂回路が便利です: git stash branch tmp-login。Git は、退避したときのコミットからブランチを作り、そこで変更を適用します。そこでは競合は起きません。その後、通常の merge で元のブランチに統合します。
誤って削除した stash を復旧する
git stash drop や clear を誤って実行しても、すべてが失われたわけではありません。記録はコミットだったので、ガベージコレクションで消えるまでは「宙に浮いた」オブジェクトとして見つけられます。
git fsck --no-reflog | grep "dangling commit"
git show 8c1d2e3 # stash かどうか確認
git stash apply 8c1d2e3
今しがた drop や pop で削除した場合、ハッシュはコマンドの出力に表示されています。Dropped refs/stash@{0} (...) の行です。そのハッシュを git stash apply に渡せば戻せます。
stash の内部の仕組み
仕組みを知ると、データを失う不安が和らぎます。stash の各記録は、普通のコミットであり、参照 refs/stash がそれを指しています。親は2つあります。退避したときの HEAD のコミットと、インデックスの状態を表す別のコミットです。-u を使った場合は、未追跡のファイルを表す3つ目の親が加わります。stash@{N} という一覧は、refs/stash の参照ログにすぎません。
そのため、stash はコミットを受け付けるどんなコマンドにも渡せます。たとえば git diff stash@{0}、git show stash@{0}:src/auth.js(記録にある1つのファイルの内容)、git checkout stash@{0} -- src/auth.js(記録全体を適用せずに1つのファイルだけ取り出す)などです。削除された記録も、ガベージコレクションが働くまではオブジェクトデータベースに残ります。
stash と他の Git コマンド
一部のコマンドは、自分で変更を退避してくれます。git rebase --autostash や git pull --rebase --autostash は、開始前に未コミットの変更を退避し、終了後に戻します。これを常に有効にするには、設定 rebase.autoStash true と merge.autoStash true を使います。戻すときに競合が起きると、Git は通常の stash のスタックに記録を残し、それを知らせます。
git switch でブランチを切り替えるときも、必ずしも stash は必要ありません。変更したファイルが、ブランチ間で違いのないファイルなら、Git はそのまま変更を持って移動します。stash が必要になるのは、「Your local changes would be overwritten by checkout」と表示されたときや、変更を別のブランチに持ち込みたくないときです。
よくある間違い
- 新規ファイルが退避されなかった。 追跡されていないので、
-uが必要です。 - stash を長期保管場所として使う。 1か月後には、「WIP on main」というラベルのない記録が10件並ぶことになります。長く続く作業は、別のブランチにコミットしてください。少なくともリモートに送れます。
- 競合の後で記録を残したままにした。 後で同じ記録をもう一度適用してしまうことがあります。競合を解決したら、必ず
dropしてください。 - stash はサーバーと同期されない。 記録はローカルのリポジトリにだけあり、
git pushでは送られません。プロジェクトのフォルダを消すと、stash も消えます。 - 違う記録を適用した。
popの前に、git stash listとgit stash show -pで中身を確認してください。
まとめ
git stash は、コミットしていない変更を失わずに作業ディレクトリを空けるための、素早い方法です。git stash push -u -m "説明" で退避し、git stash pop で戻します。apply は保険が必要なとき、git stash list と show -p は取り違えを防ぐために使います。stash を倉庫にしないでください。長い作業にはコミットとブランチを使います。タスクの切り替えと stash の扱いは、Gitコースで実践的に扱っています。
よくある質問
stash を別のブランチで適用できますか?
はい。stash はブランチに属していないので、目的のブランチに移動してから git stash pop か apply を実行します。変更が重なる場合は、通常の競合になります。
git stash はインデックスの変更も保存しますか?
保存します。ただし、通常の pop や apply では、すべてが未ステージの変更として戻ります。インデックスの状態も戻すには git stash apply --index を使います。
stash は何件まで保存できますか?
厳密な上限はありません。ただし、reflog の保存期間を超えた古い記録は、ガベージコレクションで削除される可能性があります。実際には、スタックに数件の新しい記録だけを残すのが望ましいです。
1つのファイルだけを stash するには?
push の後ろに -- とパスを付けます: git stash push -m 説明 -- src/config.js。そのファイルだけが退避され、他の変更は作業ディレクトリに残ります。
stash と一時的なコミットの違いは何ですか?
コミットはブランチに属し、サーバーに送って、amend や rebase で後から直せます。stash はローカルだけにある専用のスタックに保存され、短時間の切り替えに向いています。 </content>