- Git
- 技術 💻
git stashの使い方|全コマンド実行結果つきで退避と復元を解説
git stashとは、コミットしていない変更を一時的に退避し、作業ツリーをきれいな状態に戻すGitのコマンドです。「作業の途中で別ブランチの修正を頼まれた」「pullしたいのに手元の変更がじゃまをする」といった場面で、変更をいったん棚に置き、あとで pop や apply で取り出せます。
この記事では、Git 2.43.0(Ubuntu 24.04の標準パッケージ)で全コマンドを実際に実行した出力を載せながら、基本の push -m から、未追跡ファイルを含める -u、ステージ済みだけを退避する --staged、一部だけを退避する -p、popでのコンフリクト、うっかり消したstashの復元までを解説します。stash・WIPコミット・worktreeのどれを使うべきかの判断表もまとめました。
git stashの早見表
| やりたいこと | コマンド | 補足 |
|---|---|---|
| 変更を退避(メモつき) | git stash push -m "メモ" |
git stash だけでも同じ動き(メモなし) |
| 未追跡ファイルも退避 | git stash push -u |
--include-untracked と同じ |
| 無視ファイルも含めて全部 | git stash push -a |
.gitignore 対象も含む |
| ステージ済みだけ退避 | git stash push --staged |
Git 2.35以降 |
| 一部の変更だけ退避 | git stash push -p |
変更のかたまりごとに選ぶ |
| 特定ファイルだけ退避 | git stash push -m "メモ" -- app.py |
-- の後にパス |
| 一覧を見る | git stash list |
新しいものが stash@{0} |
| 中身を見る | git stash show -p stash@{0} |
-p で差分を表示 |
| 取り出して消す | git stash pop |
コンフリクト時は消えずに残る |
| 取り出して残す | git stash apply |
複数ブランチに当てたいとき |
| ステージ状態も復元 | git stash pop --index |
既定ではステージ状態は戻らない |
| 1つ消す/全部消す | git stash drop stash@{1} / git stash clear |
clearは確認なし |
| 新しいブランチで取り出す | git stash branch <名前> |
コンフリクトを避けやすい |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「git stashの使い方|全コマンド実行結果つきで退避と復元を解説」
典型シナリオ:switchできないときに退避する
feature/login ブランチで作業中に、hotfix ブランチへ移ろうとした場面です。同じファイルを変更していると、switchは止まります。以下はすべて実際の出力です。
$ git switch hotfix
error: Your local changes to the following files would be overwritten by checkout:
app.py
Please commit your changes or stash them before you switch branches.
Aborting
$ git stash push -m "login WIP"
Saved working directory and index state On feature/login: login WIP
$ git switch hotfix
Switched to branch 'hotfix'
hotfixでの作業が終わったら、元のブランチに戻って取り出します。
$ git switch feature/login
Switched to branch 'feature/login'
$ git stash pop
On branch feature/login
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: app.py
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (d1d3162cfb3dcef0521d29d395c767f2966bfaed)
これがstashの基本の使い方です。最後の Dropped ... (d1d3162...) に出ているハッシュは、あとで復元が必要になったときの手がかりになります。
git stash push -m:メモをつけて退避する
git stash save は非推奨になり、現在は git stash push が正式な書き方です(save もまだ動きますが、パス指定ができないなど機能が限られます)。
$ git status --short
M README.md
M app.py
?? notes.txt
$ git stash push -m "login: README and app WIP"
Saved working directory and index state On feature/login: login: README and app WIP
$ git status --short
?? notes.txt
$ git stash list
stash@{0}: On feature/login: login: README and app WIP
?? notes.txt(未追跡ファイル)は退避されずに残っています。既定のstashは「Gitが追跡しているファイルの変更」だけが対象です。
メモは必ず付けるのがおすすめです。メモなしだと WIP on feature/login: <直前のコミット> という名前になり、複数たまると見分けがつきません。
一覧と中身の確認:list と show
$ git stash show
README.md | 2 ++
app.py | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
$ git stash show -p stash@{0}
diff --git a/README.md b/README.md
index fc72a5c..1fe9f44 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
# demo
+
+login feature WIP
diff --git a/app.py b/app.py
index d58c878..77e577f 100644
--- a/app.py
+++ b/app.py
@@ -1,2 +1,2 @@
def hello():
- return "hello"
+ return "hello, world"
show はファイル一覧、show -p は差分そのものを表示します。番号を省略すると stash@{0}(最新)が対象です。
-u と -a:未追跡・無視ファイルも退避する
-u(--include-untracked)
$ git stash push -u -m "notes.txt (untracked)"
Saved working directory and index state On feature/login: notes.txt (untracked)
$ git status --short
$ git stash list
stash@{0}: On feature/login: notes.txt (untracked)
stash@{1}: On feature/login: login: README and app WIP
ここで意外な結果がありました。-u で退避したstashを show しても、何も表示されません。
$ git stash show stash@{0}
$ git stash show --include-untracked stash@{0}
notes.txt | 1 +
1 file changed, 1 insertion(+)
未追跡ファイルは別のコミットとして保存されるため、show にも --include-untracked(-u)が必要です。「中身が空のstashがある」と思って消してしまわないよう注意します。
-a(--all)
.gitignore で無視しているファイル(ここでは debug.log)は、-u でも対象外です。
$ git stash push -u -m "try -u"
No local changes to save
$ git status --short --ignored
!! debug.log
$ git stash push -a -m "with ignored files"
Saved working directory and index state On feature/login: with ignored files
$ git status --short --ignored
-a は無視ファイルまで含めるので、node_modules やビルド成果物があると巨大なstashになります。普段は -u で十分です。
--staged:ステージ済みの変更だけを退避する
Git 2.35で追加されたオプションです。git add した変更だけを退避し、未ステージの変更は手元に残します。
$ git status --short
M README.md
M app.py
$ git stash push --staged -m "only staged"
Saved working directory and index state On feature/login: only staged
$ git status --short
M app.py
$ git stash show stash@{0}
README.md | 1 +
1 file changed, 1 insertion(+)
「コミットに入れる予定の変更」と「試しに書いた変更」を分けたいときに便利です。
-p と パス指定:一部だけを退避する
-p(--patch)
1つのファイルに2か所の変更があるとき、-p で変更のかたまり(hunk)ごとに選べます。1つ目は y(退避する)、2つ目は n(退避しない)と答えました。
$ printf "y\nn\n" | git stash push -p -m "only import"
@@ -1,4 +1,5 @@
import os
+import sys
# line 1
# line 2
(1/2) Stash this hunk [y,n,q,a,d,j,J,g,/,e,?]? @@ -14,4 +15,4 @@ import os
# line 12
def bye():
- return "bye"
+ return "see you"
(2/2) Stash this hunk [y,n,q,a,d,K,g,/,e,?]?
Saved working directory and index state On feature/login: only import
$ git diff
@@ -14,4 +14,4 @@ import os
# line 12
def bye():
- return "bye"
+ return "see you"
(出力の一部、diffのヘッダー行を省略)退避したのは import sys だけで、bye() の変更は手元に残りました。なお、変更同士が近い(数行以内)と1つのかたまりにまとめられるため、別々に選べないことがあります。その場合は s(分割)を試します。
パス指定
$ git stash push -m "app only" -- app.py
Saved working directory and index state On feature/login: app only
$ git status --short
M README.md
-- の後に書いたファイルだけが退避されます。-- がないと、メモの一部がパスとして解釈されることがあるので、付けておくと確実です。
apply と pop の違い
git stash apply |
git stash pop |
|
|---|---|---|
| 変更を作業ツリーに戻す | する | する |
| stashを一覧から消す | 消さない | 成功したら消す |
| コンフリクトしたとき | 残る | 消さずに残る |
| 向いている場面 | 同じ変更を複数ブランチに当てる、念のため残したい | 普段の取り出し |
実際に apply した後の一覧です(ヒント行は省略)。stashが残っています。
$ git stash apply stash@{1}
On branch feature/login
Changes not staged for commit:
modified: README.md
modified: app.py
$ git stash list
stash@{0}: On feature/login: notes.txt (untracked)
stash@{1}: On feature/login: login: README and app WIP
ステージ状態は既定では戻らない
上の例で、退避前にステージしていた README.md が「未ステージ」として戻っている点に注目してください。ステージ状態まで戻したいときは --index を付けます。
$ git status --short
M README.md
M app.py
$ git stash push -m "index test"
$ git stash apply --index
$ git status --short
M README.md
M app.py
(push と apply のメッセージは省略)
--index を付けると、M(ステージ済み)と M(未ステージ)の区別がそのまま戻りました。
drop・clear で整理する
$ git stash list
stash@{0}: On feature/login: exp 3
stash@{1}: On feature/login: exp 2
stash@{2}: On feature/login: exp 1
$ git stash drop stash@{1}
Dropped stash@{1} (34e9dd0a5cd4bfb79ac59e1c9daf0109fe24920f)
$ git stash list
stash@{0}: On feature/login: exp 3
stash@{1}: On feature/login: exp 1
$ git stash clear
$ git stash list
dropすると後ろの番号が繰り上がります。続けて番号指定で操作するときは、git stash list で確認し直すと安心です。clear は確認なしで全件消えます。
popでコンフリクトしたときの解決
退避中に、同じ行を別のコミットで変更した場合です。
$ git stash push -m "timeout 60"
Saved working directory and index state On feature/login: timeout 60
(別の人の変更 TIMEOUT = 45 がコミットされる)
$ git stash pop
Auto-merging config.py
CONFLICT (content): Merge conflict in config.py
On branch feature/login
Unmerged paths:
(use "git restore --staged <file>..." to unstage)
(use "git add <file>..." to mark resolution)
both modified: config.py
no changes added to commit (use "git add" and/or "git commit -a")
The stash entry is kept in case you need it again.
$ cat config.py
<<<<<<< Updated upstream
TIMEOUT = 45
=======
TIMEOUT = 60
>>>>>>> Stashed changes
The stash entry is kept のとおり、コンフリクト時はstashが消えずに残ります。解決の手順は次のとおりです。
(config.py を編集して TIMEOUT = 60 に決める)
$ git add config.py
$ git restore --staged config.py
$ git status --short
M config.py
$ git stash drop
Dropped refs/stash@{0} (bcd632c2d016723913b342de6ad69edcf8a81af7)
| 手順 | 目的 |
|---|---|
| ファイルを編集して印を消す | どちらの内容にするか決める |
git add |
「解決済み」とGitに伝える |
git restore --staged |
まだコミットしないなら、ステージから外して通常の変更に戻す |
git stash drop |
手動で消す(popは消していないため) |
最後の drop を忘れると、同じstashが残り続けます。
git stash branch:新しいブランチで取り出す
退避後に元のブランチが大きく進んだ場合は、stashを作った時点のコミットから新しいブランチを作って取り出すと、コンフリクトを避けられます。
$ git stash branch experiment/timeout-90 stash@{0}
Switched to a new branch 'experiment/timeout-90'
On branch experiment/timeout-90
Changes not staged for commit:
modified: config.py
Dropped stash@{0} (09d83cbfb8e3e28b0e687b2e7ee21dda7456fb7b)
(ヒント行は省略)成功するとstashは自動で消えます。
消したstashを復元する
drop や clear で消したstashも、Gitのごみ掃除(gc)が走るまではデータが残っています。消したstashはどこからも参照されないコミットになるので、git fsck で探せます。
$ git fsck --unreachable --no-reflogs | grep commit
unreachable commit 827be76d91c779925e0f570e5331613f9d5e6975
unreachable commit 853e04dd36874c015a48f0342b4bae20dc2a7210
unreachable commit 34e9dd0a5cd4bfb79ac59e1c9daf0109fe24920f
...(計12件)
候補が多いときは、stashのメッセージで絞り込みます。
$ git log --oneline --no-walk $(git fsck --unreachable --no-reflogs | awk '/commit/ {print $3}') | grep "On feature/login"
893705c On feature/login: only first hunk
1d46571 On feature/login: exp 3
34e9dd0 On feature/login: exp 2
d1d3162 On feature/login: login WIP
d1a3793 On feature/login: exp 1
ed0b2b8 On feature/login: index test
見つけたハッシュを git stash store で一覧に戻せます。
$ git stash store -m "restored: exp 2" 34e9dd0a5cd4bfb79ac59e1c9daf0109fe24920f
$ git stash list
stash@{0}: restored: exp 2
$ git stash pop
...
Dropped refs/stash@{0} (34e9dd0a5cd4bfb79ac59e1c9daf0109fe24920f)
drop時に表示されたハッシュ(Dropped ... (34e9dd0...))が分かっていれば、fsckを使わず git stash apply 34e9dd0 で直接取り出せます。ターミナルの履歴に残っていないか先に確認すると早道です。
stash・WIPコミット・worktreeの使い分け
| 状況 | おすすめ | 理由 |
|---|---|---|
| 数分〜数時間だけ別ブランチを見る | stash | 手軽。履歴を汚さない |
| 1日以上離れる、PCを替える可能性がある | WIPコミット | ブランチに残るのでpushして共有・バックアップできる |
| 2つのブランチを同時に開いておきたい | worktree | 別ディレクトリで並行作業でき、退避が不要 |
| 変更の一部だけ脇に置きたい | stash -p / --staged | 細かく選べる |
| 試しの変更を別の案として残したい | stash branch または新ブランチ | 名前付きで管理できる |
stashはローカルにしか保存されず、名前も番号なので、長く置くほど忘れやすくなります。1日以上置くならWIPコミットにして、あとで git reset --soft HEAD~1 やスカッシュでまとめるほうが安全です。
worktreeを使うと、変更を持ったまま別ブランチを並行して開けます(実際の出力)。
$ git status --short
M README.md
$ git worktree add ../demo2-hotfix hotfix
Preparing worktree (checking out 'hotfix')
HEAD is now at 830971c hotfix: greeting
$ git -C ../demo2-hotfix status --short --branch
## hotfix
$ git status --short --branch
## feature/login
M README.md
元のディレクトリの変更(M README.md)はそのままで、hotfix側はきれいな状態で開けました。使い終わったら git worktree remove ../demo2-hotfix で片付けます。
よくある失敗と対処
| 症状 | 原因 | 対処 |
|---|---|---|
No local changes to save |
変更が未追跡・無視ファイルだけ | -u または -a を付ける |
| 新規ファイルが退避されず残る | 既定では未追跡ファイルは対象外 | -u を付ける |
| popしたらステージが外れた | 既定ではステージ状態は復元しない | --index を付ける |
| popしたのにstashが残っている | コンフリクトした | 解決後に git stash drop |
show の中身が空 |
未追跡ファイルだけのstash | git stash show -u |
| どのstashか分からない | メモなしで退避 | push -m でメモを付ける習慣に |
| 間違えてdrop・clearした | 参照が消えた | git fsck で探して git stash store |
よくある質問
Q. git stash と git stash push は違いますか?
A. 引数なしの git stash は git stash push と同じ動きです。メモやオプション、パスを指定するときは push を明示します。git stash save は非推奨です。
Q. stashはリモートにpushされますか?
A. されません。stashはローカルの refs/stash に保存されます。別のPCに移したい場合はWIPコミットにするか、Git 2.51で追加された git stash export / git stash import を使う方法があります(この記事の検証環境のGit 2.43.0には含まれないため未実行)。
Q. stashはいつまで残りますか?
A. 手動で消すまで一覧に残ります。ただし古いstashは内容を忘れやすく、ブランチの進み具合によってはコンフリクトも増えるので、使い終わったらすぐ pop か drop で整理するのがおすすめです。
Q. 別のブランチでstashを取り出しても大丈夫ですか?
A. できます。stashは「退避時の変更の差分」なので、どのブランチにも apply できます。コンフリクトが心配なときは git stash branch で退避時点から新しいブランチを作ると安全です。
まとめ
- git stashは、コミット前の変更を一時的に退避するコマンド。基本は
git stash push -m "メモ"とgit stash pop - 未追跡ファイルは
-u、無視ファイルまでなら-a。-uのstashはshow -uで中身を見る --stagedでステージ済みだけ、-pやパス指定で一部だけ退避できる- popはコンフリクト時にstashを残す。解決後に
git stash dropを忘れずに - ステージ状態まで戻すなら
--index - 消したstashは
git fsck --unreachableとgit stash storeで復元できる - 短時間はstash、長く置くならWIPコミット、並行作業ならworktree
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「git stashの使い方|全コマンド実行結果つきで退避と復元を解説」
株式会社RJCでは、Gitの使い方のような日々の小さな工夫を、プロジェクトの中で仲間と共有し合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。